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

Java 靜態代理機制深度解析:JDK 動態代理、CGLIB、Javassist 三大實現原理與代碼實戰

     編輯:Java 相關知識

靜態代理 vs 動態代理

  • 靜態代理:代理類編譯期就存在 (`.class` 文件),如手寫 Wrapper 類。
  • 動態代理:運行時通過反射/字節碼生成代理類,**本文實為動態代理**,但標題沿用「靜態代理」舊稱,實指「AOP 代理實現機制」。

三大主流實現:JDK Proxy (接口代理)、CGLIB (繼承代理)、Javassist (字節碼操作)。

核心接口對比

1. JDK 動態代理:接口代理標準實現

目標接口與實現


interface Calculator { int add(int a, int b); int sub(int a, int b); }
class CalculatorImpl implements Calculator {
    public int add(int a, int b) { return a + b; }
    public int sub(int a, int b) { return a - b; }
}

InvocationHandler 實現


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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[Before] " + method.getName() + " args=" + Arrays.toString(args));
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("[After] " + method.getName() + " result=" + result);
        return result;
    }
}

創建代理並調用


Calculator target = new CalculatorImpl();
Calculator proxy = (Calculator) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LoggingHandler(target)
);
proxy.add(3, 5); // 輸出 Before/After 日誌

關鍵限制

  • 目標類必須實現接口。
  • 代理類實現目標接口,繼承 `Proxy`,不能繼承其他類 (Java 單繼承)。
  • 只能代理接口方法,無法代理類中未定義在接口的公共方法。

2. CGLIB:繼承代理,無需接口

MethodInterceptor 實現 (方式一:代理實現接口)


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

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB Before] " + method.getName());
        // 方式 A: 反射調用目標 (較慢)
        // Object result = method.invoke(target, args);
        // 方式 B: MethodProxy 快速調用 (推薦),需傳入 target 而非 obj (obj 是代理對象,會遞歸)
        Object result = proxy.invoke(target, args);
        System.out.println("[CGLIB After] " + method.getName());
        return result;
    }
}

Enhancer 創建代理


// 目標類無需接口
class SimpleService { public void doWork() { System.out.println("Working..."); } }

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SimpleService.class); // 繼承目標類
enhancer.setCallback(new CglibInterceptor(new SimpleService()));
SimpleService proxy = (SimpleService) enhancer.create();
proxy.doWork();

關鍵點:MethodProxy.invoke vs invokeSuper

框架核心接口方法簽名關鍵差異
JDKInvocationHandlerObject invoke(Object proxy, Method method, Object[] args)基於接口,代理實現目標接口
CGLIBMethodInterceptorObject intercept(Object obj, Method method, Object[] args, MethodProxy proxy)基於繼承,代理繼承目標類,MethodProxy 加速調用
JavassistMethodHandlerObject invoke(Object self, Method m, Method proxy, Object[] args)字節碼編輯,生成實現接口/繼承類的具體 Class

避坑:在 `intercept` 中調用 `method.invoke(obj, args)` 會導致無限遞歸 (obj 是代理,再次觸發 intercept),必須用 `proxy.invoke(target, args)` 或 `proxy.invokeSuper(obj, args)`。

3. Javassist:字節碼級別靈活生成

方式一:ProxyFactory + MethodHandler (簡單)


class JavassistHandler implements MethodHandler {
    private final Object target;
    public JavassistHandler(Object target) { this.target = target; }

    @Override
    public Object invoke(Object self, Method m, Method proxy, Object[] args) throws Throwable {
        System.out.println("[Javassist] " + m.getName());
        return proxy.invoke(target, args); // proxy 替代 m,避免遞歸
    }
}

// 創建
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(CalculatorImpl.class); // 或 setInterfaces
factory.setHandler(new JavassistHandler(new CalculatorImpl()));
Calculator proxy = (Calculator) factory.createClass().newInstance();

方式二:動態生成 Java 源碼字符串編譯 (極致靈活,可運行時定義邏輯)


public class DynamicProxyGenerator {
    public static Object newProxyInstance(String interfaceName, String implClassName, String handlerClassName) throws Exception {
        ClassPool cp = ClassPool.getDefault();
        CtClass iface = cp.get(interfaceName);
        CtClass proxyClass = cp.makeClass(interfaceName + "$DynamicProxy_" + System.nanoTime());
        proxyClass.addInterface(iface);

        // 為接口每個方法生成方法體:調用 Handler.invoke
        for (Method m : Class.forName(interfaceName).getMethods()) {
            String methodCode = generateMethodCode(m, implClassName, handlerClassName);
            CtMethod cm = CtNewMethod.make(methodCode, proxyClass);
            proxyClass.addMethod(cm);
        }
        return proxyClass.toClass().newInstance();
    }

    private static String generateMethodCode(Method m, String implClass, String handlerClass) {
        // 生成類似:
        // public int add(int a0, int a1) {
        //   Handler h = new HandlerImpl();
        //   Object ret = h.invoke(ImplClass.class.newInstance(), ImplClass.class.getMethods()[0], new Object[]{($w)a0, ($w)a1});
        //   return ((Integer)ret).intValue();
        // }
        // ($w) 基本類型自動裝箱
    }
}

優勢:生成的類文件可直接 `javap` 查看、調試、存盤;無運行時反射開銷 (方法體直接調用 Handler);可實現極複雜的 AOP 邏輯編織。

三大框架綜合對比

方法用途參數 obj場景
proxy.invoke(target, args)調用目標類原始方法目標實例代理繼承目標類,攔截器持有目標引用
proxy.invokeSuper(obj, args)調用父類 (目標類) 方法代理實例 (obj)代理繼承目標類,攔截器無目標引用,直接調用父類邏輯
維度JDK ProxyCGLIBJavassist
前提條件目標實現接口目標類非 final、方法非 final無特殊限制
代理類型實現接口繼承目標類實現接口或繼承類
性能 (調用開銷)反射調用,較慢FastClass 機制,較快直接調用,最快 (接近原生)
生成速度快 (JDK 內置)中 (ASM 字節碼)較慢 (編譯源碼/字節碼)
最終方法處理無問題無法代理 final 方法可處理
包大小0 (JDK 內置)~300KB (cglib.jar + asm.jar)~700KB (javassist.jar)
Spring 默認選擇有接口用 JDK無接口/強制用 CGLIBSpring 不內置,需額外引入

最佳實踐建議

  1. 優先 JDK Proxy:面向接口編程,天然解耦,無第三方依賴。
  2. 無接口/需代理具體類方法:使用 CGLIB (Spring Boot 2.x 默認 `proxy-target-class=true`)。
  3. 極致性能/複雜字節碼操作:Javassist 或直接用 ASM/ByteBuddy。
  4. 避免 final 類/方法:殺死 CGLIB 代理可能。
  5. 序列化問題:動態代理類默認不可序列化,需實現 `Serializable` 並處理 `writeReplace`。

總結

Java 動態代理三巨頭各有千秋:JDK Proxy 標準輕量、CGLIB 繼承強大、Javassist 字節碼靈活。理解 `InvocationHandler`/`MethodInterceptor`/`MethodHandler` 核心契約、MethodProxy.invoke/invokeSuper 遞歸陷阱、字節碼生成原理,是掌握 Spring AOP、RPC 框架、ORM 懶加載等中間件核心機制的鑰匙。

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