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

Java 動態代理深度解析:JDK vs CGLIB 實戰對比

     編輯:Java 程式設計入門

一、動態代理核心價值

在不修改目標類別原始碼前提下,攔截方法呼叫,織入橫切關注點(事務、日誌、權限、快取、監控)。Spring AOP 底層即基於動態代理。

二、JDK 動態代理 (java.lang.reflect)

2.1 核心類別

  • InvocationHandler:單一方法 invoke(proxy, method, args),攔截所有代理方法呼叫。
  • Proxy:靜態工廠 newProxyInstance(loader, interfaces, handler) 產生代理實例。

2.2 限制

只能代理介面。目標類別必須實作至少一個介面。

2.3 實戰:事務攔截器

// 目標介面
public interface UserService {
    void addUser();
    void deleteUser();
}

// 目標實作
public class UserServiceImpl implements UserService {
    public void addUser() { System.out.println("add user"); }
    public void deleteUser() { System.out.println("delete user"); }
}

// 代理處理器
public class TransactionInterceptor implements InvocationHandler {
    private final Object target;

    public TransactionInterceptor(Object target) { this.target = target; }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>> 開啟事務");
        try {
            Object result = method.invoke(target, args);
            System.out.println(">>> 提交事務");
            return result;
        } catch (Exception e) {
            System.out.println(">>> 回滾事務");
            throw e;
        }
    }
}

// 客戶端
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new TransactionInterceptor(target));

proxy.addUser();
// 輸出:
// >>> 開啟事務
// add user
// >>> 提交事務

三、CGLIB 代理 (第三方庫)

3.1 核心原理

  • 繼承目標類別,產生子類別,覆寫非 final 方法。
  • 透過 MethodInterceptor 攔截,MethodProxy.invokeSuper() 呼叫父類別邏輯。
  • 可代理類別 (無需介面),但 無法代理 final 類別/方法。

3.2 依賴

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

3.3 實戰:同樣事務邏輯

public class TransactionInterceptorCglib implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(">>> [CGLIB] 開啟事務");
        try {
            Object result = proxy.invokeSuper(obj, args); // 呼叫父類別方法
            System.out.println(">>> [CGLIB] 提交事務");
            return result;
        } catch (Exception e) {
            System.out.println(">>> [CGLIB] 回滾事務");
            throw e;
        }
    }
}

// 客戶端
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class); // 目標類別
enhancer.setCallback(new TransactionInterceptorCglib());
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();

proxy.addUser();

四、JDK vs CGLIB 深度對比

維度JDK ProxyCGLIB
代理類型介面代理類別繼承代理
目標限制需實作介面不能為 final 類別/方法
生成機制執行期生成實作介面的匿名類執行期生成子類別 (ASM 操作 bytecode)
方法攔截所有介面方法所有非 final 公開/保護方法
效能 (歷史)早期較慢 (反射調用)早期較快 (FastClass 索引)
效能 (現代 JDK 8+)invokeDynamic 優化,差距極小仍略快,但生成類較多佔 Metaspace
Spring Boot 預設spring.aop.proxy-target-class=falsetrue (預設強制 CGLIB)

五、Spring AOP 代理選擇策略

@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制 CGLIB
@EnableAspectJAutoProxy(proxyTargetClass = false) // 強制 JDK (需介面)
  • 目標類別實作介面 → 預設 JDK (可配置強制 CGLIB)。
  • 目標類別未實作介面 → 強制 CGLIB。
  • final 方法 → 兩者均無法代理,Spring 會跳過織入並警告。

六、最佳實踐

  1. 領域服務**定義介面**,實作類別實作介面 → 保留 JDK 代理彈性,利於測試替換。
  2. 避免在目標方法加 final,除非確定不需 AOP。
  3. 代理邏輯保持輕量,複雜業務移至 Service 層,攔截器只做橫切關注點。
  4. 效能敏感熱點路徑可考慮 @CompileStatic (Groovy) 或手寫代理類別。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved