為目標對象提供一個代理對象,控制對目標的訪問。客戶端不直接引用目標,通過代理間接操作,實現關注點分離:業務邏輯與横切關注點 (日誌、事務、權限、緩存) 解耦。
// 1. 共同接口
public interface BuyTicketInterface { void buyTicket(); }
// 2. 目標對象
public class TravelPeople implements BuyTicketInterface {
public void buyTicket() {
System.out.println("買 5/1 重慶→北京 14:00-24:00 航班");
}
}
// 3. 代理對象 (持有目標引用)
public class BuyTicketProxy implements BuyTicketInterface {
private TravelPeople target = new TravelPeople();
public void buyTicket() {
if (target != null) target.buyTicket(); // 可在此加前後邏輯
}
}
// 4. 客戶端使用代理
public class TicketService {
public static void main(String[] args) {
BuyTicketInterface proxy = new BuyTicketProxy();
proxy.buyTicket(); // 輸出目標邏輯
}
}
| 優點 | 缺點 |
|---|---|
| 業務類純粹,復用性高、耦合低 | 一個代理類只服務一類目標,方法多時代碼膨脹 |
| 接口新增方法,所有實現類與代理類同步修改,維護成本大 |
代理類字節碼由 JVM 運行時通過反射動態生成,不存在 .class 文件。解決靜態代理「類爆炸」與「接口變更連鎖修改」問題。
java.lang.reflect.Proxy:動態代理類父類,提供靜態工廠方法 newProxyInstance。java.lang.reflect.InvocationHandler:調用處理器接口,單一方法 invoke(proxy, method, args) 集中分派所有代理方法調用。java.lang.ClassLoader:載入動態生成的代理類字節碼。InvocationHandler,持有目標對象引用,invoke 中執行前後邏輯並反射調用目標方法。Proxy.getProxyClass(loader, interfaces) 獲取代理類 Class 對象。Constructor<?> c = clazz.getConstructor(InvocationHandler.class)。Interface proxy = (Interface) c.newInstance(handler)。簡化版 (一步到位): Proxy.newProxyInstance(loader, interfaces, handler)。
// 1. 調用處理器
public class BuyTicketHandler implements InvocationHandler {
private Object target;
public BuyTicketHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始買票"); // 前置增強
Object result = method.invoke(target, args); // 反射調用目標
System.out.println("調用買票"); // 後置增強
return result;
}
}
// 2. 工廠獲取代理
public class BuyTicketProxy {
public static BuyTicketInterface createProxy() {
BuyTicketInterface target = new TravelPeople();
InvocationHandler handler = new BuyTicketHandler(target);
return (BuyTicketInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}
// 3. 測試
BuyTicketInterface proxy = BuyTicketProxy.createProxy();
proxy.buyTicket();
// 輸出:
// 開始買票
// 買 5/1 重慶→北京 14:00-24:00 航班
// 調用買票
public final,可訪問、不可繼承。$ProxyN (N 遞增)。同一接口組合複用已生成類,提升效率。Proxy,實現目標所有接口。| 維度 | 靜態代理 | 動態代理 (JDK) |
|---|---|---|
| 代碼量 | 隨方法線性增長 | 集中在 invoke,O(1) |
| 接口變更影響 | 所有代理類需同步修改 | 無需修改代理邏輯 |
| 目標類要求 | 無 | 必須實現接口 |
| 性能 | 直接調用,極快 | 反射調用,微小開銷 |
| 適用場景 | 固定、少量方法 | 方法多、頻變、AOP 橫切關注點 |
無法代理類 (只能代理接口):因代理類已繼承
Proxy,Java 單繼承限制無法再繼承目標類。歷史遺留類無接口則無法使用。
解決方案: CGLIB / ByteBuddy 等字節碼操作庫,通過繼承目標類生成子類實現代理,無需接口。Spring AOP 默認:接口用 JDK,類用 CGLIB。
設計模式無銀彈,結合項目實際「易擴展、低耦合」為最高原則。