首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 設計模式精講:靜態代理與動態代理演進實戰

     編輯:Java 程式設計入門

代理模式核心思想

代理模式為某個對象提供一個替身,以控制對該對象的訪問。生活中常見例子:房產中介(代理)代表房東(目標)與租客(客戶端)交易,中介可附加看房安排、合同審核、租後服務等增值功能,而租客無需關心房屋維修細節。

三大核心角色:

  • 抽象主題:定義代理與真實主題的共同接口。
  • 真實主題:實現具體業務邏輯。
  • 代理主題:持有真實主題引用,在調用前後織入橫切邏輯(日誌、計時、權限、事務等)。

靜態代理:手工編寫代理類

場景:為 Tank 移動添加計時與日誌

// 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 基於繼承,針對無接口類生成子類代理。

JDK 動態代理通用實現

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 代理無接口類

// 依賴 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();

靜態 vs 動態代理對比

維度靜態代理JDK 動態代理CGLIB 動態代理
目標要求任意類必須實現接口無接口要求,不能代理 final 類/方法
生成時機編譯期運行時運行時
代碼量大(每目標一代理類)小(通用 Handler)小(通用 Interceptor)
性能直接調用,最快反射調用,JDK 8+ 優化明顯FastClass 機制,調用較快
典型應用簡單包裝、適配器Spring AOP (接口場景)Spring AOP (無接口)、Hibernate 懶加載

最佳實踐建議

  1. 優先使用 JDK 動態代理:面向接口編程是良好習慣,JDK 代理無第三方依賴。
  2. 無接口或需代理具體類:引入 CGLIB(Spring Boot 默認已傳遞)。
  3. 高性能場景:考慮字節碼生成庫(ByteBuddy、ASM)或 GraalVM 原生編譯優化。
  4. 避免濫用:代理增加調試難度,僅對橫切關注點(事務、安全、監控、緩存)使用。

彩蛋:程序員笑話

波音 777 試飛前,波音總裁邀請 IBM 技術主管同行。主管推辭:「那天是妻子生日。」總裁怒:「膽小鬼,我還沒說日期呢!」

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved