代理模式為某個對象提供一個替身,以控制對該對象的訪問。生活中常見例子:房產中介(代理)代表房東(目標)與租客(客戶端)交易,中介可附加看房安排、合同審核、租後服務等增值功能,而租客無需關心房屋維修細節。
三大核心角色:
// 1. 抽象主題
public interface Moveable {
void move();
}
// 2. 真實主題
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank moving........");
}
}
// 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 TankLogProxy implements Moveable {
private final Moveable target;
public TankLogProxy(Moveable target) { this.target = target; }
@Override
public void move() {
System.out.println("start move........");
target.move();
System.out.println("end move........");
}
}
public class TestTank {
public static void main(String[] args) {
Tank tank = new Tank();
Moveable proxy = new TankLogProxy(new TankTimeProxy(tank)); // 先計時再日誌,或相反
proxy.move();
}
}
靜態代理痛點:每個目標類需手寫代理類;新增橫切關注點(如事務、緩存)需修改或新增代理類,導致類爆炸、維護困難。
JDK 動態代理基於接口,通過 java.lang.reflect.Proxy 與 InvocationHandler 實現;CGLIB 基於繼承,針對無接口類生成子類代理。
public class LoggingTimeHandler implements InvocationHandler {
private final Object target;
public LoggingTimeHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">> 方法調用: " + method.getName() + " 開始");
long start = System.nanoTime();
try {
return method.invoke(target, args);
} finally {
long cost = System.nanoTime() - start;
System.out.println(">> 方法調用: " + method.getName() + " 結束,耗時 " + cost/1_000_000 + " ms");
}
}
}
// 使用
Moveable tank = new Tank();
Moveable proxy = (Moveable) Proxy.newProxyInstance(
Tank.class.getClassLoader(),
new Class[]{Moveable.class},
new LoggingTimeHandler(tank)
);
proxy.move(); // 自動織入日誌與計時
// 依賴 cglib.jar
public class TankWithoutInterface {
public void move() { System.out.println("Tank moving..."); }
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TankWithoutInterface.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;
}
});
TankWithoutInterface proxy = (TankWithoutInterface) enhancer.create();
proxy.move();
| 維度 | 靜態代理 | JDK 動態代理 | CGLIB 動態代理 |
|---|---|---|---|
| 目標要求 | 任意類 | 必須實現接口 | 無接口要求,不能代理 final 類/方法 |
| 生成時機 | 編譯期 | 運行時 | 運行時 |
| 代碼量 | 大(每目標一代理類) | 小(通用 Handler) | 小(通用 Interceptor) |
| 性能 | 直接調用,最快 | 反射調用,JDK 8+ 優化明顯 | FastClass 機制,調用較快 |
| 典型應用 | 簡單包裝、適配器 | Spring AOP (接口場景) | Spring AOP (無接口)、Hibernate 懶加載 |
波音 777 試飛前,波音總裁邀請 IBM 技術主管同行。主管推辭:「那天是妻子生日。」總裁怒:「膽小鬼,我還沒說日期呢!」