代理模式是結構型設計模式中的核心,通過引入代理對象控制對目標對象的訪問。Java 中分為 靜態代理(編譯期生成)與 動態代理(運行時生成),後者是 Spring AOP、RPC 框架的基石。本文系統對比兩類代理原理、代碼實現、優缺點,並深入剖析 JDK 動態代理源碼與 CGLIB 機制。
| 維度 | 靜態代理 | 動態代理 |
|---|---|---|
| 生成時機 | 編譯期(程序員手寫/工具生成 .java → .class) | 運行時(反射/字節碼操作動態生成字節碼) |
| 代理類形式 | 實際 .class 文件 | 內存中動態生成的 Class 對象 |
| 目標類要求 | 無特殊要求 | JDK:必須實現接口;CGLIB:不能是 final 類/方法 |
| 靈活性 | 低(一對一,固定邏輯) | 高(可攔截任意方法、統一橫切邏輯) |
| 性能 | 直接調用,無額外開銷 | 反射/Method.invoke 開銷(JDK);FastClass 索引調用(CGLIB) |
| 典型場景 | 簡單包裝、適配器 | AOP、事務、緩存、RPC、日誌、權限 |
// 1. 共同接口
public interface UserDao {
void save();
void update();
}
// 2. 目標對象
public class UserDaoImpl implements UserDao {
@Override
public void save() { System.out.println("保存數據..."); }
@Override
public void update() { System.out.println("更新數據..."); }
}
// 3. 代理對象(手寫固定邏輯)
public class UserDaoProxy implements UserDao {
private UserDao target; // 關聯目標對象
public UserDaoProxy(UserDao target) { this.target = target; }
@Override
public void save() {
System.out.println("開啟事務"); // 前置增強
target.save(); // 委托調用
System.out.println("提交事務"); // 後置增強
}
@Override
public void update() {
System.out.println("開啟事務");
target.update();
System.out.println("提交事務");
}
}
// 4. 客戶端使用
UserDao dao = new UserDaoProxy(new UserDaoImpl());
dao.save(); // 輸出:開啟事務 → 保存數據 → 提交事務
痛點:每個目標方法都需在代理中重寫,代碼冗餘、維護困難。
invoke(proxy, method, args) 統一處理邏輯newProxyInstance(loader, interfaces, handler) 生成代理實例// 1. 實現 InvocationHandler
public class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(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;
}
}
// 2. 生成代理並使用
UserDao target = new UserDaoImpl();
UserDao proxy = (UserDao) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogHandler(target));
proxy.save(); // 同樣輸出前置/後置日誌
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) {
// 1. 獲取/生成代理 Class(緩存機制)
Class> cl = getProxyClass(loader, interfaces);
// 2. 獲取構造器(唯一構造器:Proxy(InvocationHandler))
Constructor> cons = cl.getConstructor(constructorParams);
// 3. 實例化代理對象,注入 handler
return cons.newInstance(new Object[]{h});
}
生成的代理類繼承 Proxy、實現目標接口,重寫所有方法調用 handler.invoke(this, method, args)。
index = fastClass.getIndex(method) → fastClass.invoke(index, obj, args),避免反射開銷// 目標類無接口
public class UserService {
public void add() { System.out.println("添加用戶"); }
}
// CGLIB 代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public 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;
}
});
UserService proxy = (UserService) enhancer.create();
proxy.add();
final 類/方法(無法繼承/重寫)Object 方法(equals/hashCode/toString)不攔截| 條件 | 代理方式 | 配置控制 |
|---|---|---|
| 目標實現接口 | JDK 動態代理(默認) | proxy-target-class="false" (默認) |
| 目標未實現接口 / 強制類代理 | CGLIB | proxy-target-class="true" / @EnableAspectJAutoProxy(proxyTargetClass=true) |
最佳實踐:面向接口編程,優先 JDK 代理;避免對 final 方法/類使用 AOP;性能極致場景可評估 CGLIB。
MethodHandle/LambdaMetafactory 優化,調用開銷大幅降低代理模式本質是 「關注點分離」:目標對象專注業務邏輯,代理對象承載橫切邏輯。