代理模式 是結構型設計模式的核心成員,其核心思想是:為目標對象提供一個替身(代理),控制對目標對象的訪問,並在不修改目標代碼的前提下增強功能。本文以「坦克移動」為例,從靜態代理入手,層層遞進展示日誌、計時等橫切關注點的織入,並引出動態代理的必要性。
| 角色 | 說明 | 代碼體現 |
|---|---|---|
| 抽象主題 | 定義目標與代理的公共接口 | interface Moveable { void move(); } |
| 真實主題 | 核心業務邏輯實現 | class Tank implements Moveable |
| 代理主題 | 持有真實主題引用,實現接口,增強功能 | class TankTimeProxy implements Moveable |
// 1. 抽象契約
public interface Moveable {
void move();
}
// 2. 真實主題:坦克核心邏輯
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank moving........");
// 模擬耗時操作
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
}
}
// 3. 代理主題:計時增強
public class TankTimeProxy implements Moveable {
private final Moveable target; // 組合持有目標
public TankTimeProxy(Moveable target) {
this.target = target;
}
@Override
public void move() {
long start = System.currentTimeMillis();
System.out.println("time1=" + start);
target.move(); // 委託調用
long end = System.currentTimeMillis();
System.out.println("time2=" + end);
System.out.println("執行耗時: " + (end - start) + "ms");
}
}
// 4. 客戶端測試
public class TestTank {
public static void main(String[] args) {
Tank tank = new Tank();
Moveable proxy = new TankTimeProxy(tank);
proxy.move(); // 輸出:time1 → Tank moving → time2 → 耗時
}
}
靜態代理最大的優勢在於接口一致性:所有代理都實現 Moveable,因此可以任意組合、嵌套。
// 日誌代理
public class TankLogProxy implements Moveable {
private final Moveable target;
public TankLogProxy(Moveable target) { this.target = target; }
@Override
public void move() {
System.out.println(">>> 開始移動 [LOG]");
target.move();
System.out.println("<<< 結束移動 [LOG]");
}
}
// 組合使用:先計時,再日誌(或反過來)
public class TestTank {
public static void main(String[] args) {
Tank tank = new Tank();
Moveable timed = new TankTimeProxy(tank); // 第1層:計時
Moveable logged = new TankLogProxy(timed); // 第2層:日誌
logged.move();
}
}
// 輸出順序:
// >>> 開始移動 [LOG]
// time1=...
// Tank moving........
// time2=...
// 執行耗時: ...ms
// <<< 結束移動 [LOG]
這正是 責任鏈/裝飾器 風格的靜態組合,順序靈活可控。
若需為 Tank 添加:事務、權限、緩存、重試、熔斷、監控……每個功能都要手寫一個 XxxProxy 類。
這正是 動態代理(JDK Proxy、CGLIB、ByteBuddy)誕生的動力:運行時生成代理類,集中處理橫切邏輯。
| 維度 | 靜態代理 | 動態代理 |
|---|---|---|
| 代理類生成時間 | 編譯期 | 運行時 |
| 目標類要求 | 實現接口 | JDK: 必須實現接口;CGLIB: 可代理類 |
| 性能 | 極高(直接調用) | 略低(反射/字節碼生成開銷,可忽略) |
| 適用場景 | 固定、少量、性能極度敏感的增強 | 通用 AOP、框架級攔截、批量增強 |
| 調試難度 | 低(可直接斷點) | 中(需查看生成字節碼) |
Spring AOP、Guice AOP、AspectJ 皆是動態代理的工程化封裝。開發者僅需:
@Aspect
@Component
public class TankMonitorAspect {
@Around("execution(* com.demo.Tank.move(..))")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
log.info(">>> Tank.move 開始");
Object result = pjp.proceed();
log.info("<<< Tank.move 結束,耗時 {} ms", (System.nanoTime() - start) / 1e6);
return result;
}
}
一個切面替代 N 個靜態代理類,實現業務邏輯與橫切關注點完全解耦。
程序員笑話:波音 777 試飛邀請 IBM 技術總監,總監推說「那天老婆過生日」。波音總裁怒:「怯懦鬼,我還沒說日期呢!」