為目標對象提供一個代理,控制對目標的訪問。兩大優勢:
代理類在編譯期就已存在(.java → .class),通常由開發者手寫或工具生成。代理類與目標類實現同一接口或繼承同一父類。
// 接口
public interface Sell {
void sell();
void ad();
}
// 目標類(被代理)
public class Vendor implements Sell {
public void sell() { System.out.println("廠家賣貨"); }
public void ad() { System.out.println("廠家廣告"); }
}
// 靜態代理類
public class BusinessAgent implements Sell {
private Vendor vendor; // 聚合目標對象
public BusinessAgent(Vendor vendor) { this.vendor = vendor; }
public void sell() {
System.out.println("代理:售前檢查");
vendor.sell(); // 委託
System.out.println("代理:售後服務");
}
public void ad() {
System.out.println("代理:廣告投放前審核");
vendor.ad();
System.out.println("代理:廣告效果統計");
}
}
| 優點 | 缺點 |
|---|---|
| 簡單直觀,易理解 | 一個目標類對應一個代理類,代碼冗餘嚴重 |
| 可針對單方法定制邏輯 | 接口新增方法,目標與代理都要改,維護成本高 |
| 無運行時性能損耗 | 無法統一處理橫切關注點(如所有方法加事務) |
代理類在運行時通過反射動態生成(JDK Proxy 或 CGLIB),無需手寫代理類源碼。
invoke(proxy, method, args)// 1. 目標類必須實現接口
public class Vendor implements Sell { ... }
// 2. 實現 InvocationHandler(統一橫切邏輯)
public class DynamicProxy implements InvocationHandler {
private Object target; // 持有目標對象引用
public DynamicProxy(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before: " + method.getName());
Object result = method.invoke(target, args); // 反射調用目標方法
System.out.println("after: " + method.getName());
return result;
}
}
// 3. 客戶端生成代理並調用
public class Main {
public static void main(String[] args) {
Vendor vendor = new Vendor();
DynamicProxy handler = new DynamicProxy(vendor);
// 關鍵:生成實現 Sell 接口的代理類實例
Sell proxy = (Sell) Proxy.newProxyInstance(
Sell.class.getClassLoader(), // 類加載器
new Class[]{Sell.class}, // 代理實現的接口列表
handler // 調用處理器
);
proxy.sell(); // 實際調用 handler.invoke()
proxy.ad();
}
}
Client → Proxy Instance (實現 Sell)
↓
InvocationHandler.invoke()
↓
Method.invoke(target, args) → Vendor.sell()
本質:兩層靜態代理疊加。InvocationHandler 是 Vendor 的代理;生成的 Proxy 實例是 InvocationHandler 的代理。
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 會在項目目錄生成 $Proxy0.class,可用 jad 反編譯查看
Enhancer + MethodInterceptorproxyTargetClass=true)| 場景 | 推薦 | 理由 |
|---|---|---|
| 目標類固定、方法少、邏輯差異大 | 靜態代理 | 代碼可讀性高,無反射開銷 |
| 統一處理所有方法(事務、日誌、權限) | 動態代理 | 一處定義,處處生效,符合 AOP 思想 |
| 目標類無接口、第三方庫類 | CGLIB | 繼承代理,無需接口 |
| 高性能、高併發核心鏈路 | 靜態代理 / 手寫字節碼 | 避免反射調用開銷 |
final 方法不可被代理(CGLIB 也無法攔截)Serializable 並處理 writeReplace$Proxy0.class 反編譯,理解代理結構@Aspect + @Around 替代手寫 InvocationHandler,聲明式事務 @Transactional 即典型應用代理模式是 AOP 的基石。靜態代理 適合簡單、固定場景;JDK 動態代理 基於接口、反射生成,是 Spring AOP 默認選擇;CGLIB 補足無接口場景。理解 InvocationHandler.invoke() 統一攔截入口,掌握「雙層代理」原理,即可舉一反三應對各種框架源碼閱讀與自定義攔截需求。