前言
最近在寫項目的時候,又寫到很久沒寫的 AOP 切面實現一個需求,又想到上次同學面試的時候被問到了 Spring AOP 的實現原理是什麼,以前就知道是用了代理模式,但是也沒有進行過多的去研究,剛好碰到了也就研究一下代理模式。
什麼是代理模式
代理模式就是通過一個代理對象來間接訪問目標對象,這樣可以在不改變目標對象的情況下,為它添加一些額外的功能或行為。簡單來説,代理就是“替身”,它在幕後幫目標對象做一些額外的事。要擴展功能,就不需要更改源目標的代碼了,只需要在代理類增加就可以了。
代理模式的分類
- 靜態代理:代理類在編譯時就已經確定,通常需要手動創建代理類。
- 動態代理:代理類在運行時動態生成,不需要提前創建,代理對象的創建由代理框架(如 JDK 動態代理或 CGLIB)控制。
| 特點 | 靜態代理 | 動態代理 |
|---|---|---|
| 代理類創建時機 | 代理類在編譯時已經確定,需要手動創建代理類 | 代理類在運行時動態生成,不需要提前創建 |
| 實現方式 | 手動編寫代理類,代理類與目標類關係固定 | 使用代理框架(如 JDK 動態代理、CGLIB)在運行時創建代理類 |
| 目標類要求 | 目標類必須實現接口(或繼承某個類) | JDK 動態代理:目標類必須實現接口;CGLIB:不需要接口 |
靜態代理的實現
靜態代理就是在程序編譯時就創建好代理類,並讓代理類和目標類實現相同的接口。代理類通過調用目標類的方法來完成任務,同時可以在調用前後添加一些額外的操作。
示例:靜態代理模式
接口定義:
public interface UserService {
void save(String username);
void update(Long id, String username);
void delete(Long id);
}
目標類實現:
public class UserServiceImpl implements UserService {
@Override
public void save(String username) {
System.out.println("增加用户: " + username);
}
@Override
public void update(Long id, String username) {
System.out.println("編輯用户: " + username);
}
@Override
public void delete(Long id) {
System.out.println("刪除用户: " + id);
}
}
靜態代理類:
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public UserServiceProxy(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void save(String userName) {
// 在調用實際方法之前,做一些操作(如記錄日誌)
System.out.println("日誌:在添加用户之前");
userService.save(userName); // 調用真實對象的方法
System.out.println("日誌:在添加用户之後");
}
@Override
public void update(Long id, String userName) {
System.out.println("日誌:在更新用户之前");
userService.update(id, userName);
System.out.println("日誌:在更新用户之後");
}
@Override
public void delete(Long id) {
System.out.println("日誌:在刪除用户之前");
userService.delete(id); // 調用真實對象的方法
System.out.println("日誌:在刪除用户之後");
}
}
使用代理
public class Main {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserService userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.save("yunzhi");
userServiceProxy.update(1L, "yunzhi");
userServiceProxy.delete(1L);
}
}
打印結果:
日誌:在添加用户之前
增加用户: yunzhi
日誌:在添加用户之後
日誌:在更新用户之前
編輯用户: yunzhi
日誌:在更新用户之後
日誌:在刪除用户之前
刪除用户: 1
日誌:在刪除用户之後
結論:
靜態代理的實現方式相對直觀,代碼也較為簡潔易懂。然而,這種模式的缺點也非常明顯:當需要代理的接口數量增多時,每增加一個接口就必須創建一個相應的代理類,這樣會導致大量的代理類代碼,造成系統的臃腫和維護上的困難。
動態代理的實現
與靜態代理不同,動態代理是在運行時通過代理框架(如 JDK 動態代理或 CGLIB)來生成代理對象,不需要提前編寫代理類。
JDK動態代理
JDK 動態代理,當目標類實現了接口時,並將接口的方法委託給目標類的實現。
JDK動態代理的實現步驟:
1.定義接口和實現類。
2.創建InvocationHandler實現,負責目標方法的攔截和增強
3.使用 Proxy 類創建代理對象。
創建一個 JdkLoggingInvocationHandler 類,統一實現日誌處理代理
public class JdkLoggingInvocationHandler implements InvocationHandler {
private Object target;
public JdkLoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日誌:調用 " + method.getName() + " 方法");
// 調用目標對象的方法
Object result = method.invoke(target, args);
// 在方法調用後添加日誌
System.out.println("日誌:執行 " + method.getName() + " 方法完成");
return result;
}
}
使用代理
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
handler);
proxy.save("yunzhi");
proxy.update(1L, "yunzhi");
proxy.delete(1L);
}
打印結果:
日誌:調用 save 方法
增加用户: yunzhi
日誌:執行 save 方法完成
日誌:調用 update 方法
編輯用户: yunzhi
日誌:執行 update 方法完成
日誌:調用 delete 方法
刪除用户: 1
日誌:執行 delete 方法完成
注意點
這裏我們要注意當前的代理對象不是原始對象了,通過 Proxy.newProxyInstance 創建的代理對象,實際上並不是 UserServiceImpl 或其他實現類的實例,它是通過 Proxy 類在運行時生成的,實現了目標接口,並通過 InvocationHandler 實現了方法的增強邏輯。
目標類沒有接口就不能使用JDK代理
這裏我們把 UserService 移除掉,不是實現接口的形式
public class UserServiceImpl {
}
使用代理
UserServiceImpl userService = new UserServiceImpl();
JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);
UserServiceImpl proxy = (UserServiceImpl) Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(),
new Class<?>[]{UserServiceImpl.class},
handler);
這裏我們可以發現,如果目標類不是接口的話就沒辦法進行代理增強。
結論
在靜態代理中,每個目標類都需要一個單獨的代理類,代理類的代碼重複且難以維護。
在JDK動態代理中,你只需要編寫一個 InvocationHandler 來統一處理所有目標類的代理方法,增強邏輯可以複用,但是必須要求目標類是有接口實現。
CGLIB 動態代理
CGLIB 代理可以對任何沒有實現接口的類進行代理,因為它是通過繼承目標類並重寫方法來實現的,在實際的場景中,有一些業務不總是實現接口,為了增強這些沒有接口的類,所以 Spring 使用 CGLIB 的方式實現動態代理。
截取 Spring 項目中使用 AOP 的對象
CGLIB 動態代理的實現步驟:
1.定義目標類
CGLIB 代理是基於類的,而不是接口的,所以目標類不需要實現接口。
2.創建 MethodInterceptor 實現
MethodInterceptor 用於攔截目標方法,並進行增強邏輯。這個類相當於 JDK 動態代理中的 InvocationHandler。
3.使用 Enhancer 創建代理對象
Enhancer 是 CGLIB 的核心類,用於創建動態代理對象。它通過繼承目標類並重寫目標方法來實現代理。
創建一個 CglibLoggingMethodInterceptor 類,統一實現日誌處理代理
public class CglibLoggingMethodInterceptor implements MethodInterceptor {
private Object target;
public CglibLoggingMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("日誌:調用 " + method.getName() + " 方法");
Object result = proxy.invokeSuper(obj, args); // 調用父類的方法
System.out.println("日誌:執行 " + method.getName() + " 方法完成");
return result;
}
}
使用代理
public static void main(String[] args) {
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 創建 CGLIB 代理的 Enhancer 對象
Enhancer enhancer = new Enhancer();
// 設置代理的目標類(即 UserServiceImpl 類)
// CGLIB 通過繼承這個目標類來生成代理類
enhancer.setSuperclass(UserServiceImpl.class);
// 設置方法攔截器,代理會調用這個攔截器
// 這裏使用了一個自定義的 CglibLoggingMethodInterceptor 來攔截方法並添加日誌增強
enhancer.setCallback(new CglibLoggingMethodInterceptor(userServiceImpl));
// 創建代理對象
// 通過 Enhancer.create() 創建代理對象,此對象會繼承 UserServiceImpl 類並重寫其方法
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.save("yunzhi");
proxy.update(1L, "yunzhi");
proxy.delete(1L);
}
打印結果:
日誌:調用 save 方法
增加用户: yunzhi
日誌:執行 save 方法完成
日誌:調用 update 方法
編輯用户: yunzhi
日誌:執行 update 方法完成
日誌:調用 delete 方法
刪除用户: 1
日誌:執行 delete 方法完成
注意點
這裏我們要注意當前的代理對象不是原始對象了,通過 Enhancer 類來創建代理對象。這個代理對象是通過繼承目標類 UserServiceImpl 來創建的,代理對象並不是原始的 UserServiceImpl 對象,而是通過繼承生成的新類。
總結:
-
靜態代理:
- 適用於簡單場景,代碼重複且不靈活。
-
JDK 動態代理:
- 代理對象實現了目標接口,實際的目標對象沒有改變。
- 代理對象通過
Proxy類動態生成,並通過InvocationHandler處理方法增強。
-
CGLIB 代理:
- 代理對象繼承自目標類並重寫目標方法,實際的目標類沒有改變。
- 代理對象通過
Enhancer動態生成,並通過MethodInterceptor處理方法增強。