動態代理三大核心組件
- 目標對象:實現業務邏輯的真實類 (
UserDAOImpl)。
- 介面:代理與目標共同實現的契約 (
UserDAO),代理實現此介面實現多態。
- InvocationHandler:代理邏輯集中處理器,攔截所有方法調用。
代碼結構速覽
// 1. 介面
interface UserDAO { void save(User u); void delete(); }
// 2. 目標實現
class UserDAOImpl implements UserDAO {
public void save(User u) { System.out.println("user saved!"); }
public void delete() { System.out.println("user deleted"); }
}
// 3. 通用攔截器
class LogInterceptor implements InvocationHandler {
private Object target; // 持有目標引用
public void setTarget(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " start"); // 前置增強
Object result = method.invoke(target, args); // 調用目標方法
System.out.println(method.getName() + " end"); // 後置增強
return result;
}
}
// 4. 客戶端創建代理
UserDAO target = new UserDAOImpl();
LogInterceptor handler = new LogInterceptor();
handler.setTarget(target);
UserDAO proxy = (UserDAO) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 類加載器
target.getClass().getInterfaces(), // 實現的介面列表
handler // 處理器
);
proxy.delete(); // 觸發代理流程
執行流程深度追蹤 (堆棧視角)
- proxy.delete() 調用
- JVM 分發到代理類
$Proxy0 的 delete() 方法
$Proxy0.delete() 內部構造 Method 對象,調用 handler.invoke(proxy, method, args)
- LogInterceptor.invoke() 執行:
- 前置邏輯:
beforeMethod(method) → 打印 "delete start"
- 核心反射調用:
method.invoke(target, args)
- 後置邏輯:打印 "delete end"
- method.invoke() 觸發反射機制,最終調用 UserDAOImpl.delete()
- 目標方法返回,反射返回,
invoke() 返回,代理方法返回,調用者收到結果
$Proxy0 類結構揭秘
- 類名格式:
$Proxy + 數字,運行時由 ProxyGenerator 動態生成字節碼。
- 繼承
java.lang.reflect.Proxy,實現目標所有介面 (UserDAO)。
- 每個介面方法生成一個對應方法,方法體固定模板:
public final void delete() throws {
try {
handler.invoke(this, m1, null); // m1 是 Method 對象,靜態緩存
} catch (Error|RuntimeException e) { throw e; }
catch (Throwable e) { throw new UndeclaredThrowableException(e); }
}
- 關鍵點:代理類不包含業務邏輯,純粹負責分發給
InvocationHandler。
動態生成過程簡析
// Proxy.newProxyInstance 核心邏輯
Class> cl = getProxyClass0(loader, interfaces); // 1. 獲取/生成代理類
Constructor> cons = cl.getConstructor(InvocationHandler.class); // 2. 獲取構造器
return cons.newInstance(h); // 3. 實例化,注入 handler
getProxyClass0 使用弱引用緩存,同介面組合只生成一次。
- 生成字節碼使用
ProxyGenerator.generateProxyClass,輸出 .class 二進制數組,再 defineClass 載入 JVM。
動態代理 vs CGLIB 對比
| 維度 | JDK 動態代理 | CGLIB |
| 前提條件 | 目標必須實現一個或多個介面 | 無需介面,繼承目標類 |
| 原理 | 生成實現介面的類 | 生成目標類的子類,重寫方法 |
| final 方法 | 介面方法不可 final | 無法代理 final/private 方法 |
| 性能 (歷史) | 早期較慢 (反射調用) | 早期較快 (FastClass 索引) |
| 現狀 (JDK 8+) | invokedynamic 優化,性能接近 | 仍廣泛用於 Spring AOP (無介面場景) |
Spring AOP 中的應用
Spring 默認:有介面用 JDK,無介面用 CGLIB。可通過 @EnableAspectJAutoProxy(proxyTargetClass=true) 強制 CGLIB。理解 JDK 動態代理原理,是掌握 Spring AOP 切面執行順序、代理對象注入、自調用失效 (@Transactional 失效) 等問題的基石。
調試技巧:保存生成的代理類字節碼
// JVM 參數
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
// 當前目錄下會生成 $Proxy0.class,可用 jad/fernflower 反編譯查看
- 上一篇文章: Java 線程池核心原理與 ThreadPoolExecutor 源碼深度解析
- 下一篇文章: Java 並發同步工具類實戰:CountDownLatch、CyclicBarrier、Semaphore、BlockingQueue
Copyright ©
程式碼之家 All Rights Reserved