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

Java 代理模式深度解析:靜態代理到動態代理的演進之路

     編輯:Java 程式設計入門

代理模式核心角色

  • Subject (接口):定義代理與被代理的公共契約
  • RealSubject (目標對象):真實業務邏輯實現
  • Proxy (代理對象):持有 RealSubject 引用,增強預處理/後處理/過濾/分發

一、靜態代理:手寫代理類

// 接口
interface CountDao { void queryCount(); }

// 目標實現
class CountDaoImpl implements CountDao {
    public void queryCount() { System.out.println("查看賬戶..."); }
}

// 事務代理
class CountTxProxy implements CountDao {
    private final CountDao target;
    CountTxProxy(CountDao target) { this.target = target; }
    public void queryCount() {
        System.out.println("tx begin");
        target.queryCount();
        System.out.println("tx commit");
    }
}

// 日誌代理
class CountLogProxy implements CountDao {
    private final CountDao target;
    CountLogProxy(CountDao target) { this.target = target; }
    public void queryCount() {
        System.out.println("log start");
        target.queryCount();
        System.out.println("log end");
    }
}

組合多重代理

CountDao dao = new CountDaoImpl();
CountDao tx = new CountTxProxy(dao);
CountDao log = new CountLogProxy(tx); // 外層日誌,內層事務
log.queryCount();
// log start → tx begin → 查看賬戶 → tx commit → log end

**優點**:結構清晰、編譯期類型安全。
**缺點**:每新增一個橫切關注點需手寫代理類、組合代碼冗長、目標類必須實現接口。

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

JDK 動態代理 (基於接口)

class LoggingInvocationHandler implements InvocationHandler {
    private final Object target;
    LoggingInvocationHandler(Object target) { this.target = target; }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after: " + method.getName());
        return result;
    }
}

// 使用
CountDao dao = (CountDao) Proxy.newProxyInstance(
        CountDao.class.getClassLoader(),
        new Class[]{CountDao.class},
        new LoggingInvocationHandler(new CountDaoImpl()));
dao.queryCount();

限制:**目標類必須實現接口**,無法代理普通類。

CGLIB 代理 (基於繼承,Spring 默認)

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CountDaoImpl.class); // 針對具體類
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("cglib before");
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        System.out.println("cglib after");
        return result;
    }
});
CountDao proxy = (CountDao) enhancer.create();
proxy.queryCount();

原理:生成目標類子類,攔截父類方法調用。**不能代理 final 類/方法**。

三、靜態 vs 動態對比

維度靜態代理JDK 動態代理CGLIB
代理類生成時機編譯期運行時運行時
目標類要求實現接口實現接口非 final 類
多橫切點組合手動嵌套攔截器鏈攔截器鏈
性能直接調用,最快反射調用,較慢FastClass 機制,較快
調試難度易(源碼可見)難(動態生成)難

四、Spring AOP 的選擇邏輯

// 簡化版 Spring 邏輯
if (targetClass.hasInterfaces()) {
    return new JdkDynamicAopProxy(config); // 默認走 JDK
} else {
    return new CglibAopProxy(config);      // 無接口走 CGLIB
}
// 可強制使用 CGLIB: @EnableAspectJAutoProxy(proxyTargetClass = true)

五、最佳實踐建議

  1. 面向接口編程:天然支持 JDK 代理,解耦實現
  2. 優先 JDK 代理:無第三方依賴、JDK 內置、模塊化系統友好
  3. 需代理具體類/無接口:引入 CGLIB (Spring Boot 已內傳)
  4. 避免 final 方法:阻礙 CGLIB 攔截,亦影響 JDK default 方法代理
  5. 輕量級替代:簡單場景用裝飾器模式或方法攔截器(如 Guice AOP)

理解代理模式從靜態到動態的演進,掌握 JDK/CGLIB 兩大機制差異,是掌握 Spring AOP、中間件擴展、RPC 框架的基石。

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