概述
面試的時候,java開發必問的知識點是spring,spring中核心的是IOC和AOP。問到AOP的實現原理的時候,我們都知道是代理模式,卻對代理模式一知半解,今天就來記記筆記。
下面借鑑一張代理模式的導圖:
靜態代理
首先假設一種業務場景,需要實現對使用者進行CRUD的操作,所以我們建立了一個UserService介面和UserServiceImpl的實現類。程式碼如下:
public interface UserService {
void addUser();
void updateUser();
}
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("新增一個使用者");
}
@Override
public void updateUser() {
System.out.println("更新一個使用者");
}
}
複製程式碼
現在我們需要在每次對使用者資訊進行增加、刪除和更新操作的時候記錄一下日誌,用靜態代理的方式,我們需要建立一個靜態代理類,將被代理物件(目標物件)傳入,然後建立需要增強的方法,如addUser和updateUser,實現記錄日誌的功能
public class UserStaticProxy {
private UserService userService;
public UserStaticProxy(UserService userService) {
this.userService=userService;
}
public void addUser() {
userService.addUser();
System.out.println("列印一條日誌");
}
}
複製程式碼
測試
public class StaticProxyTest {
public static void main(String [] args) {
UserService userService = new UserServiceImpl();
UserStaticProxy staticProxy = new UserStaticProxy(userService);
staticProxy.addUser();
}
}
複製程式碼
執行結果:
靜態代理的缺點
1、介面增加方法,代理類需要同步維護。
2、介面越多,需要建立的代理類就越多。比如以後我們有TeacherService,StudentService,就需要建立TeacherStaticProxy,StudentStaticProxy,這樣就增加了程式碼量。
JDK動態代理
其實動態代理和靜態代理的本質是一樣的,最終程式執行時都需要生成一個代理物件例項。只不過靜態代理是採用硬編碼的方式在程式執行之前就建立好代理類,而動態代理是執行時動態生成的方式。
JDk幫我們實現了動態代理,使用的是newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
該方法中接收三個引數:
ClassLoader loader,:指定當前目標物件使用類載入器
Class<?>[] interfaces,:代理類需要實現的介面列表
InvocationHandler h:呼叫處理程式,將目標物件的方法分派到該呼叫處理程式
程式碼示例:
public class DynamicProxy implements InvocationHandler{
private Object target;//目標物件
public Object bind(Object target) {
this.target=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
//執行目標物件的方法
Object result = method.invoke(target,args);
//實現擴充套件功能
System.out.println("列印一下日誌");
return result;
}
}
複製程式碼
bind方法簡單封裝jdk的newProxyInstance(),並返回目標介面物件。invoke方法負責增強目標物件的方法,實現擴充套件功能。
測試類:
public class DynamicProxyTest {
public static void main(String [] args) {
DynamicProxy dynamicProxy = new DynamicProxy();
UserService userService = (UserService) dynamicProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
}
}
複製程式碼
執行結果:
可見,動態代理解決了靜態代理的兩個缺點。
CGLib動態代理
上面兩種代理方式,目標物件UserServiceimpl都實現了一個介面,如果只是一個普通的類,沒有實現任何介面,該如何進行代理呢?這就引出了CGLib動態代理。CGLib動態代理也叫子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。
要用cglib代理,需要匯入相應的包,好在spring已經整合了它,引入spring即可。
代理類:
public class CGLibProxy implements MethodInterceptor {
private Object target;
public Object bind(Object target) {
this.target=target;
Enhancer enhancer = new Enhancer();
//設定父類
enhancer.setSuperclass(this.target.getClass());
//設定回撥函式
enhancer.setCallback(this);
//建立並返回子類物件
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("列印一下日誌");
return object;
}
}
複製程式碼
測試類:
public class CGLibProxyTest {
public static void main(String [] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
UserService userService = (UserService) cgLibProxy.bind(new UserServiceImpl());
userService.addUser();
userService.updateUser();
}
}
複製程式碼
執行結果:
使用CGLib代理有兩個注意點:
1、目標物件不能被final關鍵字修飾,因為被final關鍵字修飾的物件是不可繼承的。 2、目標物件的方法如果是final/staic,那麼該方法是不會被攔截(不能被增強),即不會執行目標物件額外的業務方法。
總結
今天的筆記就記到這裡,感謝前來閱讀的你們,我們一起學習,一起進步。