在 Java 開發中,動態代理是實現 AOP(面向切面編程)的核心技術,廣泛應用于日志記錄、事務管理、權限控制等場景。其中,JDK 動態代理和 CGlib 是兩種最常用的動態代理實現方式。本文將從原理、區別、使用場景等方面深入解析這兩種技術,幫助開發者更好地理解和選擇。
一、動態代理的基本概念
動態代理是一種在運行時動態生成代理類的技術,無需手動編寫代理類代碼。其核心作用是:在不修改目標對象代碼的前提下,對目標對象的方法進行增強(如在方法執行前后添加日志、性能監控等邏輯)。
簡單來說,動態代理就像給目標對象 “套了一層殼”,所有對目標對象的調用都會先經過這層殼,從而實現增強邏輯的統一管理。
二、JDK 動態代理
1. 原理
JDK 動態代理是 Java 官方提供的代理技術,基于接口和反射機制實現:
- 要求目標類必須實現一個或多個接口。
- 運行時,JDK 會動態生成一個實現了目標類所有接口的代理類($ProxyXXX)。
- 代理類通過調用
InvocationHandler
接口的invoke
方法,將增強邏輯與目標方法的執行結合起來。
2. 核心接口:InvocationHandler
public interface InvocationHandler {// proxy:代理對象本身// method:目標方法// args:目標方法的參數Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
代理類的所有方法調用都會被轉發到invoke
方法,開發者需在該方法中實現增強邏輯。
3. 實現示例
步驟 1:定義目標接口和實現類
// 目標接口
public interface UserDao {void add();
}// 目標類(實現接口)
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("執行UserDao的add方法");}
}
步驟 2:實現 InvocationHandler
public class JdkProxyHandler implements InvocationHandler {// 目標對象(被代理的原始對象)private Object target;public JdkProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 增強邏輯:方法執行前System.out.println("JDK代理:方法執行前(日志記錄)");// 執行目標方法Object result = method.invoke(target, args);// 增強邏輯:方法執行后System.out.println("JDK代理:方法執行后(日志記錄)");return result;}
}
步驟 3:生成代理對象并使用
public class JdkProxyDemo {public static void main(String[] args) {// 目標對象UserDao target = new UserDaoImpl();// 生成代理對象(通過Proxy類的newProxyInstance方法)UserDao proxy = (UserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 類加載器target.getClass().getInterfaces(), // 目標類實現的接口new JdkProxyHandler(target) // 增強邏輯處理器);// 調用代理對象的方法(實際會執行增強邏輯+目標方法)proxy.add();}
}
輸出結果
JDK代理:方法執行前(日志記錄)
執行UserDao的add方法
JDK代理:方法執行后(日志記錄)
三、CGlib 動態代理
1. 原理
CGlib(Code Generation Library)是一個第三方字節碼生成庫,基于繼承實現動態代理:
- 不要求目標類實現接口,通過生成目標類的子類作為代理類。
- 運行時,CGlib 會動態生成目標類的子類,并重寫目標類的非 final 方法。
- 代理邏輯通過
MethodInterceptor
接口的intercept
方法實現。
2. 核心接口:MethodInterceptor
public interface MethodInterceptor {// obj:代理對象(目標類的子類)// method:目標方法// args:目標方法的參數// proxy:方法代理對象(用于調用目標方法)Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
3. 實現示例
步驟 1:添加 CGlib 依賴(Maven)
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
步驟 2:定義目標類(無需實現接口)
public class UserService {public void update() {System.out.println("執行UserService的update方法");}
}
步驟 3:實現 MethodInterceptor
public class CglibInterceptor implements MethodInterceptor {// 目標對象private Object target;public CglibInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 增強邏輯:方法執行前System.out.println("CGlib代理:方法執行前(性能監控)");// 執行目標方法(通過代理對象調用父類方法)Object result = proxy.invokeSuper(obj, args);// 增強邏輯:方法執行后System.out.println("CGlib代理:方法執行后(性能監控結束)");return result;}
}
步驟 4:生成代理對象并使用
public class CglibDemo {public static void main(String[] args) {// 目標對象UserService target = new UserService();// CGlib增強器(用于生成代理類)Enhancer enhancer = new Enhancer();// 設置父類(目標類)enhancer.setSuperclass(UserService.class);// 設置回調(增強邏輯)enhancer.setCallback(new CglibInterceptor(target));// 生成代理對象(目標類的子類)UserService proxy = (UserService) enhancer.create();// 調用代理對象的方法proxy.update();}
}
輸出結果
CGlib代理:方法執行前(性能監控)
執行UserService的update方法
CGlib代理:方法執行后(性能監控結束)
四、JDK 動態代理 vs CGlib 動態代理
對比維度 | JDK 動態代理 | CGlib 動態代理 |
---|---|---|
底層技術 | 基于接口 + 反射 | 基于繼承 + 字節碼生成(ASM 庫) |
目標類要求 | 必須實現接口 | 可無接口(但不能是 final 類 / 方法) |
代理類生成 | 實現目標接口的代理類 | 繼承目標類的子類 |
效率 | 反射調用,效率較低(JDK 8+后優化明顯) | 直接調用子類方法,效率較高 |
靈活性 | 僅能代理接口方法 | 可代理類中所有非 final 方法 |
依賴 | JDK 內置,無需額外依賴 | 需要引入 CGlib 庫 |
關鍵區別總結:
- 接口依賴:JDK 動態代理強制要求目標類實現接口,CGlib 無此限制。
- 性能:在多次調用場景下,CGlib 效率更高(因避免了反射開銷)。
- 限制:CGlib 無法代理 final 類或 final 方法(子類無法重寫)。
五、Spring 中的選擇策略
Spring AOP 默認根據目標類是否實現接口自動選擇代理方式:
- 若目標類實現了接口:默認使用 JDK 動態代理。
- 若目標類未實現接口:自動切換為 CGlib 代理。
若需強制使用 CGlib(即使目標類有接口),可通過配置開啟:
- XML 配置:
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 注解配置:
@EnableAspectJAutoProxy(proxyTargetClass = true)
六、使用場景建議
-
優先用 JDK 動態代理:
- 目標類已實現接口。
- 追求開發便捷性(無需額外依賴)。
- 方法調用頻率不高的場景。
-
選擇 CGlib:
- 目標類無接口,或需代理非接口方法。
- 對性能要求高(如高頻調用的核心服務)。
- 可接受引入第三方依賴。
七、總結
JDK 動態代理和 CGlib 是動態代理的兩大主流實現,各有優劣:
- JDK 動態代理基于接口,簡單易用,是 Spring 的默認選擇。
- CGlib 基于繼承,性能更優,適合無接口或高性能需求的場景。
理解兩者的原理和區別,有助于在實際開發中根據場景合理選擇,也能更深入地理解 Spring AOP 等框架的底層實現。