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

Java 設計模式精講:靜態代理與動態代理(JDK / CGLIB / Javassist)全對比

     編輯:Java 相關知識

代理模式核心思想

代理模式:為目標對象提供一個代理對象,控制對真實對象的訪問。核心價值在於在不修改目標類代碼的前提下,增強/攔截方法調用。典型應用場景:

  • 權限控制、日誌記錄、事務管理(AOP 核心)。
  • 遠程調用(RPC Stub)、延遲加載(Hibernate 懶加載)。
  • 性能監控、緩存攔截。

一、靜態代理

代理類在編譯期就已存在(.class 文件),直接實現同一接口,持有真實對象引用。

// 接口
public interface IDBQuery { String request(); }

// 真實對象(構造耗時 10s 模擬重資源初始化)
public class DBQuery implements IDBQuery {
    public DBQuery() { try { Thread.sleep(10_000); } catch (Exception e) {} }
    public String request() { return "real data"; }
}

// 靜態代理:延遲初始化真實對象
public class DBQueryProxy implements IDBQuery {
    private DBQuery real;
    public String request() {
        if (real == null) real = new DBQuery(); // 首次調用才創建
        return real.request();
    }
}

// 客戶端
IDBQuery proxy = new DBQueryProxy();
System.out.println(proxy.request()); // 首次調用耗時 10s,之後極快

缺點:每個接口需手寫代理類,接口變更雙方同步修改,維護成本高。

二、動態代理:運行時生成代理類字節碼

1. JDK 動態代理 (Java 內置,基於接口)

public class JdkProxyHandler implements InvocationHandler {
    private IDBQuery target;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target == null) target = new DBQuery(); // 延遲初始化
        System.out.println("JDK Proxy: before " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("JDK Proxy: after");
        return result;
    }
    public static IDBQuery createProxy() {
        return (IDBQuery) Proxy.newProxyInstance(
            ClassLoader.getSystemClassLoader(),
            new Class[]{IDBQuery.class},
            new JdkProxyHandler()
        );
    }
}

機制:Proxy.newProxyInstance 生成實現目標接口的匿名類,將所有方法調用轉發給 InvocationHandler.invoke。限制:目標類必須實現接口。

2. CGLIB 動態代理 (基於繼承,字節碼操作庫)

// 依賴: cglib:cglib-nodep:3.3.0
public class CglibProxyFactory implements MethodInterceptor {
    private Object target;
    public Object getProxy(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 {
        System.out.println("CGLIB: before " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        System.out.println("CGLIB: after");
        return result;
    }
}

特點:無需接口,通過繼承目標類生成子類代理。無法代理 final 類/方法。

3. Javassist 動態代理 (字節碼操作框架,Spring 早期使用)

API 風格更接近 Java 源碼編程,性能介於 JDK 與 CGLIB 之間,現較少直接使用,多被框架內部集成。

三、性能與選型對比 (JDK 17+ 基準測試參考)

維度JDK ProxyCGLIBJavassist
代理類生成速度最快 (native defineClass)中等較慢
方法調用性能較慢 (反射調用)最快 (FastClass 索引直調)中等
目標類要求必須實現接口非 final 類/方法無特殊限制
適用場景接口導向編程、AOP 標準無接口類、高性能調用框架底層、複雜字節碼改寫

結論:實際業務中方法調用頻率遠高於代理創建頻率,CGLIB 調用性能優勢明顯;但 Spring Boot 2.x+ 默認 AOP 仍優先 JDK Proxy(接口規範),無接口時自動降級 CGLIB。

四、動態代理類加載原理剖析 (以 CGLIB 為例)

  1. Enhancer.create() 觸發 DefaultGeneratorStrategy.generate() 生成代理類字節碼 (byte[])。
  2. ReflectUtils.defineClass() 反射調用 ClassLoader.defineClass() 將字節碼載入 JVM。
  3. ReflectUtils.newInstance() 反射實例化代理類並返回。

全過程無磁盤 I/O,純內存操作,毫秒級完成。

五、最佳實踐建議

  1. 面向接口編程,優先 JDK Proxy,符合依賴倒置原則。
  2. 無接口舊代碼改造、第三方庫類代理 → CGLIB。
  3. 避免在 invoke/intercept 中做耗時邏輯,影響被代理方法響應。
  4. 注意 this 指向問題:代理內部調用自身方法不會觸發攔截器,需注入代理自身引用或使用 AOP 框架暴露代理 (AopContext.currentProxy())。

總結

代理模式是框架級開發的基石。掌握「靜態代理 → JDK 動態代理 → CGLIB/Javassist」演進脈絡,理解字節碼生成、類加載、方法分派機制,能讓開發者在設計擴展點、排查 AOP 失效、優化 RPC 框架時遊刃有餘。

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