Java 設計模式精講:代理模式
代理模式為物件提供一個替身,控制對真實物件的存取,並在不修改目標代碼前提下增強功能。本文詳細對比靜態代理、JDK 動態代理、CGLIB 動態代理三種實現方式的原理、代碼結構、優缺點與適用場景。
一、模式角色與核心思想
- 抽象主題:定義真實主題與代理共同介面。
- 真實主題:實作核心業務邏輯。
- 代理主題:持有真實主題引用,實作相同介面,可在調用前後織入橫切邏輯(驗證、日誌、事務、緩存等)。
二、靜態代理
代碼結構
// 1. 共同介面
public interface TicketManager {
void soldTicket();
void changeTicket();
void returnTicket();
}
// 2. 真實主題
public class TicketManagerImpl implements TicketManager {
public void soldTicket() { System.out.println("售票"); }
public void changeTicket() { System.out.println("改簽"); }
public void returnTicket() { System.out.println("退票"); }
}
// 3. 代理類(編譯期確定)
public class StaticProxyTicketManager implements TicketManager {
private TicketManager target;
public StaticProxyTicketManager(TicketManager target) { this.target = target; }
public void soldTicket() {
checkIdentity(); // 前置增強
target.soldTicket(); // 委派真實主題
log(); // 後置增強
}
// changeTicket/returnTicket 同理
private void checkIdentity() { System.out.println("身份驗證"); }
private void log() { System.out.println("日誌..."); }
}
// 4. 客戶端
public class Client {
public static void main(String[] args) {
TicketManager tm = new StaticProxyTicketManager(new TicketManagerImpl());
tm.soldTicket();
}
}
優缺點
| 優點 | 缺點 |
| 結構簡單、易理解、不改動目標類 | 每個目標類需一個代理類 → 類爆炸;代碼冗余;編譯期靜態綁定,彈性差 |
三、JDK 動態代理
核心機制
- 運行時透過
java.lang.reflect.Proxy 生成實作目標介面的匿名類。
- 代理類必須實作
InvocationHandler,所有方法調用轉發至 invoke(proxy, method, args)。
- 前提:目標類必須實作至少一個介面。
代碼實現
public class JdkDynamicProxy implements InvocationHandler {
private Object target;
public Object newProxyInstance(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkIdentity(); // 前置
Object result = method.invoke(target, args); // 反射調用真實方法
log(); // 後置
return result;
}
private void checkIdentity() { System.out.println("身份驗證"); }
private void log() { System.out.println("日誌..."); }
}
// 客戶端
TicketManager tm = (TicketManager) new JdkDynamicProxy()
.newProxyInstance(new TicketManagerImpl());
tm.soldTicket();
優缺點
| 優點 | 缺點 |
| 單一代理類服務多介面;運行時動態生成;解耦目標與增強邏輯 | 僅能代理介面;反射調用效率略低;代碼可讀性稍弱 |
四、CGLIB 動態代理
核心機制
- 底層使用 ASM 字節碼操作框架,生成目標類的子類,覆寫父類非 final 方法。
- 無需介面,直接代理類;但 final 類/方法不可代理。
- 核心類:
Enhancer(增強器)、MethodInterceptor(攔截器)、MethodProxy(高效反射調用)。
代碼實現
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 繼承目標類
enhancer.setCallback(this); // 設置攔截器
return enhancer.create(); // 生成代理實例
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
checkIdentity(); // 前置
Object result = proxy.invokeSuper(obj, args); // 快速調用父類方法
log(); // 後置
return result;
}
private void checkIdentity() { System.out.println("身份驗證"); }
private void log() { System.out.println("日誌..."); }
}
// 客戶端
TicketManager tm = (TicketManager) new CglibProxy()
.getInstance(new TicketManagerImpl());
tm.soldTicket();
優缺點
| 優點 | 缺點 |
| 無需介面;生成類後調用效率高(FastClass 機制);Spring AOP 預設選擇 | 無法代理 final 類/方法;繼承機制限制;生成類開銷較大(可緩存緩解) |
五、三種代理對比總結
| 維度 | 靜態代理 | JDK 動態代理 | CGLIB 動態代理 |
| 代理目標 | 具體類 | 實作介面的類 | 任意非 final 類 |
| 生成時機 | 編譯期 | 運行時 | 運行時 |
| 核心技術 | 手寫類 | 反射 + Proxy | ASM 字節碼 + 繼承 |
| 效能 | 最高(直接調用) | 中等(反射) | 高(FastClass,生成後) |
| 適用場景 | 固定少量目標、教學演示 | 介面導向設計、標準化攔截 | 無介面類、高效能 AOP、Spring 預設 |
六、實戰選型建議
- 目標類實作介面 → 優先 JDK 動態代理(標準、輕量)。
- 目標類無介面或需代理具體類方法 → CGLIB。
- Spring AOP:
proxy-target-class=false(預設 JDK)、true(強制 CGLIB)。
- 極簡場景/教學 → 靜態代理最直觀。
