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

Java 動態代理兩大核心實現:JDK Proxy 與 CGLIB 深度實戰

     編輯:Java 綜合教學

Java 動態代理兩大核心實現:JDK Proxy 與 CGLIB 深度實戰

動態代理是 Spring AOP、RPC 框架、中間件攔截器的基石。本文通過完整案例,對比 JDK 動態代理(基於接口) 與 CGLIB 代理(基於繼承) 兩種核心實現機制,剖析原理差異、代碼結構與適用場景。

核心概念:什麼是動態代理?

代理模式核心:為目標對象提供一個替身,在不修改目標代碼前提下,攔截方法調用,織入橫切邏輯(日誌、事務、權限、緩存等)。動態代理指代理類在運行時動態生成,而非編譯期預定義。

案例場景:電商價格計算與日誌攔截

定義業務接口 Num 及實現 MyNum,代理目標:getProduct() 價格翻倍、show() 前後增強日誌。

public interface Num {
    void show();
    int getNum();
    int getProduct(int x);
}

public class MyNum implements Num {
    @Override public int getNum() { return 3; }
    @Override public int getProduct(int x) { return x; }
    @Override public void show() { System.out.println("底層方法打印數字99"); }
}

方式一:JDK 動態代理(基於接口)

核心類:java.lang.reflect.Proxy + InvocationHandler。

public class JdkNumProxy {
    private final Object target; // 目標實例
    public JdkNumProxy(Object target) { this.target = target; }

    public Object getProxy() {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            new Class[]{Num.class}, // 必須實現接口
            (proxy, method, args) -> {
                System.out.println("【JDK】方法前增強: " + method.getName());
                Object result = method.invoke(target, args);
                if ("getProduct".equals(method.getName())) {
                    result = (Integer) result * 2; // 業務增強:價格翻倍
                }
                System.out.println("【JDK】方法後增強: " + method.getName());
                return result;
            }
        );
    }
}

方式二:CGLIB 代理(基於繼承,無需接口)

核心庫:net.sf.cglib.proxy.Enhancer + MethodInterceptor。原理:生成目標類的子類,重寫非 final 方法攔截調用。

public class CglibNumProxy implements MethodInterceptor {
    private final Object target;
    public CglibNumProxy(Object target) { this.target = target; }

    public Object getProxy() {
        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】方法前增強: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        if ("getProduct".equals(method.getName())) {
            result = (Integer) result * 2;
        }
        System.out.println("【CGLIB】方法後增強: " + method.getName());
        return result;
    }
}

統一測試驗證

public class ProxyTest {
    public static void main(String[] args) {
        MyNum target = new MyNum();

        // JDK 代理
        Num jdkProxy = (Num) new JdkNumProxy(target).getProxy();
        System.out.println("=== JDK Proxy ===");
        System.out.println("getProduct(2) = " + jdkProxy.getProduct(2)); // 4
        jdkProxy.show();

        // CGLIB 代理
        Num cglibProxy = (Num) new CglibNumProxy(target).getProxy();
        System.out.println("\n=== CGLIB Proxy ===");
        System.out.println("getProduct(3) = " + cglibProxy.getProduct(3)); // 6
        cglibProxy.show();
    }
}

運行輸出

=== JDK Proxy ===
【JDK】方法前增強: getProduct
【JDK】方法後增強: getProduct
getProduct(2) = 4
【JDK】方法前增強: show
【JDK】方法後增強: show
底層方法打印數字99

=== CGLIB Proxy ===
【CGLIB】方法前增強: getProduct
【CGLIB】方法後增強: getProduct
getProduct(3) = 6
【CGLIB】方法前增強: show
【CGLIB】方法後增強: show
底層方法打印數字99

深度對比:JDK vs CGLIB

維度JDK 動態代理CGLIB 代理
核心機制實現接口,生成實現類繼承目標類,生成子類
前置條件目標類必須實現一個或多個接口目標類/方法不能為 final
代理對象類型實現接口,與目標類是兄弟關係繼承目標類,是子類關係
方法攔截範圍僅接口聲明的方法所有非 final 實例方法(含私有不可攔截)
性能特徵生成快(字節碼簡單),調用稍慢(反射調用)生成慢(字節碼複雜),調用快(FastClass 索引直調)
適用場景接口驅動開發、Spring AOP 默認策略無接口類、高性能調用、第三方庫擴展

Spring 的選擇策略

Spring AOP 默認:有接口用 JDK,無接口用 CGLIB。可通過 @EnableAspectJAutoProxy(proxyTargetClass=true) 強制全局 CGLIB。

常見坑點避坑指南

  • final 方法無法代理:CGLIB 無法重寫 final 方法,JDK 介面方法隱式非 final。
  • 內部方法調用失效:this.method() 繞過代理,需注入 ApplicationContextAware 獲取代理對象或用 AspectJ 編譯時增強。
  • 序列化問題:JDK 代理實現 Serializable 需額外處理;CGLIB 代理默認可序列化(若目標類可序列化)。
  • 獲取目標類真實 Class:JDK 代理 getClass() 返回 class com.sun.proxy.$ProxyX,需用 AopUtils.getTargetClass(proxy)。

總結

動態代理是元編程在 Java 的經典落地。JDK Proxy 輕量標準,CGLIB 功能強大。理解兩者機制差異與限制,結合業務場景選型,是構建高擴展性框架的關鍵能力。下一篇將實戰:基於動態代理實現聲明式事務管理器。

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