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

Spring 動態代理核心原理:JDK Proxy 與 CGLIB 深度對比

     編輯:Java 綜合教學

代理模式在 Spring AOP 中的地位

Spring AOP 的實現完全建立在動態代理之上。根據目標物件是否實作介面,Spring 預設策略:

  • 有介面 → JDK 動態代理(java.lang.reflect.Proxy)
  • 無介面(具體類) → CGLIB 代理(字節碼繼承)

可透過 @EnableAspectJAutoProxy(proxyTargetClass = true) 強制使用 CGLIB。

一、JDK 動態代理完整實戰

1. 介面與實作

public interface HelloWorld {
    void sayHello(String name);
}

public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

2. 自訂 InvocationHandler

public class CustomInvocationHandler implements InvocationHandler {
    private final Object target;

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

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

3. 建立代理並測試

public class ProxyTest {
    public static void main(String[] args) {
        // 保存生成的代理類字節碼(除錯用)
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        HelloWorld target = new HelloWorldImpl();
        CustomInvocationHandler handler = new CustomInvocationHandler(target);

        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
            ProxyTest.class.getClassLoader(),
            new Class[]{HelloWorld.class},
            handler
        );

        proxy.sayHello("Mikan");
    }
}

4. 輸出結果

Before invocation: sayHello
Hello Mikan
After invocation: sayHello

二、JDK 代理原始碼核心流程

  1. Proxy.newProxyInstance(loader, interfaces, handler)
  2. 檢查快取 proxyClassCache,命中直接返回;未命中進入 ProxyClassFactory 生成類。
  3. 生成類特徵:
    • 繼承 java.lang.Proxy,實作所有指定介面。
    • 單一構造器:protected Proxy(InvocationHandler h)。
    • 靜態區塊初始化所有介面方法的 Method 物件(含 equals/hashCode/toString)。
    • 每個代理方法僅委派 handler.invoke(this, method, args)。
  4. 反射調用構造器建立代理實例,注入 InvocationHandler。

JDK 代理限制

  • 只能代理介面,無法代理具體類(Java 單繼承限制)。
  • 生成類在同一包下(com.sun.proxy.$ProxyN),需公開介面。

三、CGLIB 動態代理實戰

1. 目標類(無介面)

public class SayHello {
    public void say() {
        System.out.println("hello everyone");
    }
}

2. 實作 MethodInterceptor

public class CglibProxy implements MethodInterceptor {
    private final Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);      // 設定父類
        enhancer.setCallback(this);         // 設定攔截器
        return enhancer.create();           // 生成子類實例
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置代理");
        Object result = proxy.invokeSuper(obj, args); // 呼叫父類方法
        System.out.println("後置代理");
        return result;
    }
}

3. 測試

public class DoCGLib {
    public static void main(String[] args) {
        SayHello proxy = (SayHello) new CglibProxy().getProxy(SayHello.class);
        proxy.say();
    }
}

4. 輸出

前置代理
hello everyone
後置代理

四、JDK vs CGLIB 全方位對比

維度JDK 動態代理CGLIB
前提條件目標類實作一個或多個介面無介面要求,但類與方法非 final
實現原理介面實作 + 委派 InvocationHandler字節碼繼承 + FastClass 索引 + MethodInterceptor
代理類關係繼承 Proxy,實作目標介面繼承目標類,覆寫非 final 方法
生成速度快(輕量級字節碼生成)較慢(複雜字節碼操作、FastClass 建立)
呼叫效能反射調用,相對較慢FastClass 直接索引,呼叫效能高
適用場景單例、介面豐富、生成頻繁單例、無介面、效能敏感、調用頻繁
final 方法介面方法隱含非 final無法代理 final 方法

五、Spring AOP 切點表達式速查

execution(修飾符? 返回型別 宣告型別.方法名(參數) 拋出異常?)

// 常用範例
execution(public * *(..))                    // 所有 public 方法
execution(* com.example.service.*.*(..))     // service 包所有方法
execution(* com.example.service.UserService.*(..)) // UserService 所有方法
execution(* com.example..*.*(..))            // com.example 及子包所有方法
execution(!private * *(..))                  // 非 private 方法
@annotation(com.example.Loggable)            // 標註 @Loggable 的方法

總結建議

  • 優先使用介面 + JDK 代理,符合面向介面設計,生成快、記憶體佔用少。
  • 無介面或需代理具體類方法時啟用 CGLIB(proxyTargetClass=true)。
  • 避免在目標類大量使用 final 方法,否則 CGLIB 無法攔截。
  • 高併發熱點方法考慮 CGLIB 提升呼叫效能,但需權衡啟動時間。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved