動態代理是 Spring AOP、RPC 框架、中間件攔截器的基石。本文通過完整案例,對比 JDK 動態代理(基於接口) 與 CGLIB 代理(基於繼承) 兩種核心實現機制,剖析原理差異、代碼結構與適用場景。
代理模式核心:為目標對象提供一個替身,在不修改目標代碼前提下,攔截方法調用,織入橫切邏輯(日誌、事務、權限、緩存等)。動態代理指代理類在運行時動態生成,而非編譯期預定義。
定義業務接口 Num 及實現 MyNum,代理目標:getProduct() 價格翻倍、show() 前後增強日誌。
public interface Num {
void show();
int getNum();
int getProduct(int x);
}
public class MyNum implements Num {
@Override public int getNum() { return 3; }
@Override public int getProduct(int x) { return x; }
@Override public void show() { System.out.println("底層方法打印數字99"); }
}
核心類:java.lang.reflect.Proxy + InvocationHandler。
public class JdkNumProxy {
private final Object target; // 目標實例
public JdkNumProxy(Object target) { this.target = target; }
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{Num.class}, // 必須實現接口
(proxy, method, args) -> {
System.out.println("【JDK】方法前增強: " + method.getName());
Object result = method.invoke(target, args);
if ("getProduct".equals(method.getName())) {
result = (Integer) result * 2; // 業務增強:價格翻倍
}
System.out.println("【JDK】方法後增強: " + method.getName());
return result;
}
);
}
}
核心庫:net.sf.cglib.proxy.Enhancer + MethodInterceptor。原理:生成目標類的子類,重寫非 final 方法攔截調用。
public class CglibNumProxy implements MethodInterceptor {
private final Object target;
public CglibNumProxy(Object target) { this.target = target; }
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 設置父類
enhancer.setCallback(this);
return enhancer.create(); // 生成代理實例
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB】方法前增強: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 調用父類方法
if ("getProduct".equals(method.getName())) {
result = (Integer) result * 2;
}
System.out.println("【CGLIB】方法後增強: " + method.getName());
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
MyNum target = new MyNum();
// JDK 代理
Num jdkProxy = (Num) new JdkNumProxy(target).getProxy();
System.out.println("=== JDK Proxy ===");
System.out.println("getProduct(2) = " + jdkProxy.getProduct(2)); // 4
jdkProxy.show();
// CGLIB 代理
Num cglibProxy = (Num) new CglibNumProxy(target).getProxy();
System.out.println("\n=== CGLIB Proxy ===");
System.out.println("getProduct(3) = " + cglibProxy.getProduct(3)); // 6
cglibProxy.show();
}
}
=== JDK Proxy ===
【JDK】方法前增強: getProduct
【JDK】方法後增強: getProduct
getProduct(2) = 4
【JDK】方法前增強: show
【JDK】方法後增強: show
底層方法打印數字99
=== CGLIB Proxy ===
【CGLIB】方法前增強: getProduct
【CGLIB】方法後增強: getProduct
getProduct(3) = 6
【CGLIB】方法前增強: show
【CGLIB】方法後增強: show
底層方法打印數字99
| 維度 | JDK 動態代理 | CGLIB 代理 |
|---|---|---|
| 核心機制 | 實現接口,生成實現類 | 繼承目標類,生成子類 |
| 前置條件 | 目標類必須實現一個或多個接口 | 目標類/方法不能為 final |
| 代理對象類型 | 實現接口,與目標類是兄弟關係 | 繼承目標類,是子類關係 |
| 方法攔截範圍 | 僅接口聲明的方法 | 所有非 final 實例方法(含私有不可攔截) |
| 性能特徵 | 生成快(字節碼簡單),調用稍慢(反射調用) | 生成慢(字節碼複雜),調用快(FastClass 索引直調) |
| 適用場景 | 接口驅動開發、Spring AOP 默認策略 | 無接口類、高性能調用、第三方庫擴展 |
Spring AOP 默認:有接口用 JDK,無接口用 CGLIB。可通過 @EnableAspectJAutoProxy(proxyTargetClass=true) 強制全局 CGLIB。
this.method() 繞過代理,需注入 ApplicationContextAware 獲取代理對象或用 AspectJ 編譯時增強。Serializable 需額外處理;CGLIB 代理默認可序列化(若目標類可序列化)。getClass() 返回 class com.sun.proxy.$ProxyX,需用 AopUtils.getTargetClass(proxy)。動態代理是元編程在 Java 的經典落地。JDK Proxy 輕量標準,CGLIB 功能強大。理解兩者機制差異與限制,結合業務場景選型,是構建高擴展性框架的關鍵能力。下一篇將實戰:基於動態代理實現聲明式事務管理器。