| 框架 | 核心接口 | 方法簽名 | 關鍵差異 |
| JDK | InvocationHandler | Object invoke(Object proxy, Method method, Object[] args) | 基於接口,代理實現目標接口 |
| CGLIB | MethodInterceptor | Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) | 基於繼承,代理繼承目標類,MethodProxy 加速調用 |
| Javassist | MethodHandler | Object invoke(Object self, Method m, Method proxy, Object[] args) | 字節碼編輯,生成實現接口/繼承類的具體 Class |
1. JDK 動態代理:接口代理標準實現
目標接口與實現
interface Calculator { int add(int a, int b); int sub(int a, int b); }
class CalculatorImpl implements Calculator {
public int add(int a, int b) { return a + b; }
public int sub(int a, int b) { return a - b; }
}
InvocationHandler 實現
class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[Before] " + method.getName() + " args=" + Arrays.toString(args));
Object result = method.invoke(target, args); // 反射調用目標
System.out.println("[After] " + method.getName() + " result=" + result);
return result;
}
}
創建代理並調用
Calculator target = new CalculatorImpl();
Calculator proxy = (Calculator) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
proxy.add(3, 5); // 輸出 Before/After 日誌
關鍵限制
- 目標類必須實現接口。
- 代理類實現目標接口,繼承 `Proxy`,不能繼承其他類 (Java 單繼承)。
- 只能代理接口方法,無法代理類中未定義在接口的公共方法。
2. CGLIB:繼承代理,無需接口
MethodInterceptor 實現 (方式一:代理實現接口)
class CglibInterceptor implements MethodInterceptor {
private final Object target;
public CglibInterceptor(Object target) { this.target = target; }
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB Before] " + method.getName());
// 方式 A: 反射調用目標 (較慢)
// Object result = method.invoke(target, args);
// 方式 B: MethodProxy 快速調用 (推薦),需傳入 target 而非 obj (obj 是代理對象,會遞歸)
Object result = proxy.invoke(target, args);
System.out.println("[CGLIB After] " + method.getName());
return result;
}
}
Enhancer 創建代理
// 目標類無需接口
class SimpleService { public void doWork() { System.out.println("Working..."); } }
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SimpleService.class); // 繼承目標類
enhancer.setCallback(new CglibInterceptor(new SimpleService()));
SimpleService proxy = (SimpleService) enhancer.create();
proxy.doWork();
關鍵點:MethodProxy.invoke vs invokeSuper
| 方法 | 用途 | 參數 obj | 場景 |
| proxy.invoke(target, args) | 調用目標類原始方法 | 目標實例 | 代理繼承目標類,攔截器持有目標引用 |
| proxy.invokeSuper(obj, args) | 調用父類 (目標類) 方法 | 代理實例 (obj) | 代理繼承目標類,攔截器無目標引用,直接調用父類邏輯 |
避坑:在 `intercept` 中調用 `method.invoke(obj, args)` 會導致無限遞歸 (obj 是代理,再次觸發 intercept),必須用 `proxy.invoke(target, args)` 或 `proxy.invokeSuper(obj, args)`。
3. Javassist:字節碼級別靈活生成
方式一:ProxyFactory + MethodHandler (簡單)
class JavassistHandler implements MethodHandler {
private final Object target;
public JavassistHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object self, Method m, Method proxy, Object[] args) throws Throwable {
System.out.println("[Javassist] " + m.getName());
return proxy.invoke(target, args); // proxy 替代 m,避免遞歸
}
}
// 創建
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(CalculatorImpl.class); // 或 setInterfaces
factory.setHandler(new JavassistHandler(new CalculatorImpl()));
Calculator proxy = (Calculator) factory.createClass().newInstance();
方式二:動態生成 Java 源碼字符串編譯 (極致靈活,可運行時定義邏輯)
public class DynamicProxyGenerator {
public static Object newProxyInstance(String interfaceName, String implClassName, String handlerClassName) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass iface = cp.get(interfaceName);
CtClass proxyClass = cp.makeClass(interfaceName + "$DynamicProxy_" + System.nanoTime());
proxyClass.addInterface(iface);
// 為接口每個方法生成方法體:調用 Handler.invoke
for (Method m : Class.forName(interfaceName).getMethods()) {
String methodCode = generateMethodCode(m, implClassName, handlerClassName);
CtMethod cm = CtNewMethod.make(methodCode, proxyClass);
proxyClass.addMethod(cm);
}
return proxyClass.toClass().newInstance();
}
private static String generateMethodCode(Method m, String implClass, String handlerClass) {
// 生成類似:
// public int add(int a0, int a1) {
// Handler h = new HandlerImpl();
// Object ret = h.invoke(ImplClass.class.newInstance(), ImplClass.class.getMethods()[0], new Object[]{($w)a0, ($w)a1});
// return ((Integer)ret).intValue();
// }
// ($w) 基本類型自動裝箱
}
}
優勢:生成的類文件可直接 `javap` 查看、調試、存盤;無運行時反射開銷 (方法體直接調用 Handler);可實現極複雜的 AOP 邏輯編織。
三大框架綜合對比
| 維度 | JDK Proxy | CGLIB | Javassist |
| 前提條件 | 目標實現接口 | 目標類非 final、方法非 final | 無特殊限制 |
| 代理類型 | 實現接口 | 繼承目標類 | 實現接口或繼承類 |
| 性能 (調用開銷) | 反射調用,較慢 | FastClass 機制,較快 | 直接調用,最快 (接近原生) |
| 生成速度 | 快 (JDK 內置) | 中 (ASM 字節碼) | 較慢 (編譯源碼/字節碼) |
| 最終方法處理 | 無問題 | 無法代理 final 方法 | 可處理 |
| 包大小 | 0 (JDK 內置) | ~300KB (cglib.jar + asm.jar) | ~700KB (javassist.jar) |
| Spring 默認選擇 | 有接口用 JDK | 無接口/強制用 CGLIB | Spring 不內置,需額外引入 |
最佳實踐建議
- 優先 JDK Proxy:面向接口編程,天然解耦,無第三方依賴。
- 無接口/需代理具體類方法:使用 CGLIB (Spring Boot 2.x 默認 `proxy-target-class=true`)。
- 極致性能/複雜字節碼操作:Javassist 或直接用 ASM/ByteBuddy。
- 避免 final 類/方法:殺死 CGLIB 代理可能。
- 序列化問題:動態代理類默認不可序列化,需實現 `Serializable` 並處理 `writeReplace`。
總結
Java 動態代理三巨頭各有千秋:JDK Proxy 標準輕量、CGLIB 繼承強大、Javassist 字節碼靈活。理解 `InvocationHandler`/`MethodInterceptor`/`MethodHandler` 核心契約、MethodProxy.invoke/invokeSuper 遞歸陷阱、字節碼生成原理,是掌握 Spring AOP、RPC 框架、ORM 懶加載等中間件核心機制的鑰匙。
- 上一篇文章: Jemalloc 優化 MySQL 與 Nginx 內存分配性能實戰指南
- 下一篇文章: Java 實作內嵌圖片郵件發送完整教學
Copyright ©
程式碼之家 All Rights Reserved