剛開始用 Spring AOP 時,以為都是用 JDK Proxy。後來發現為啥有些類別代理不了,還是要靠 CGLIB,才開始研究這東西。CGLIB 現在真的無所不在,有必要搞懂它的原理。
為什麼需要 CGLIB?
JDK Proxy 有個死穴:必須有 interface。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface UserService { void save(User user); }
public class UserServiceImpl implements UserService { public void save(User user) { } }
public class OrderService { public void createOrder(Order order) { } }
|
現實中一堆類別沒有 interface,難不成都要改程式?CGLIB 就是為了解決這個問題。
CGLIB 的核心原理
CGLIB 使用 ASM bytecode 框架,在執行時期動態生成目標類別的子類別。然後覆寫父類別的方法,加入攔截邏輯。
簡單說就是:
- 讀取目標類別的 bytecode
- 用 ASM 框架在記憶體中生成子類別的 bytecode
- 把新的 bytecode 載入 JVM
- 建立這個子類別的實例
1 2 3 4 5 6 7 8 9 10 11
| public class OrderService$$EnhancerByCGLIB$$123abc extends OrderService { private MethodInterceptor methodInterceptor; @Override public void createOrder(Order order) { methodInterceptor.intercept(this, super.createOrder, args, methodProxy); } }
|
基本使用
核心類別:Enhancer
Enhancer 負責建立代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class OrderService { public void createOrder(Order order) { System.out.println("Creating order: " + order.getId()); } }
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OrderService.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before: " + method.getName()); Object result = proxy.invokeSuper(obj, args); System.out.println("After: " + method.getName()); return result; } });
OrderService proxyService = (OrderService) enhancer.create(); proxyService.createOrder(new Order(1L));
|
輸出:
1 2 3
| Before: createOrder Creating order: 1 After: createOrder
|
MethodInterceptor 介面
MethodInterceptor 是攔截的核心,四個參數:
1 2 3 4 5 6 7
| public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { }
|
重要:用 proxy.invokeSuper() 而不是 method.invoke()
為什麼?因為 proxy 是直接呼叫父類別的方法,不經過 reflection,效能快很多。
1 2 3 4 5
| method.invoke(obj, args);
proxy.invokeSuper(obj, args);
|
實務範例:加 Log + 計時
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class LoggingInterceptor implements MethodInterceptor { private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class); @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); log.info("Entering method: {}", methodName); long startTime = System.currentTimeMillis(); try { Object result = proxy.invokeSuper(obj, args); long duration = System.currentTimeMillis() - startTime; log.info("Method {} completed in {}ms", methodName, duration); return result; } catch (Exception e) { log.error("Method {} failed", methodName, e); throw e; } } }
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new LoggingInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.save(user);
|
進階用法:Filter + 多個 Interceptor
有時候只想攔截特定方法,用 CallbackFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OrderService.class);
MethodInterceptor loggingInterceptor = (obj, method, args, proxy) -> { System.out.println("Log: " + method.getName()); return proxy.invokeSuper(obj, args); };
MethodInterceptor authInterceptor = (obj, method, args, proxy) -> { System.out.println("Auth check"); return proxy.invokeSuper(obj, args); };
CallbackFilter filter = (method) -> { if (method.getName().startsWith("create")) { return 0; } else if (method.getName().startsWith("find")) { return 1; } return 0; };
enhancer.setCallbacks(new Callback[]{authInterceptor, loggingInterceptor}); enhancer.setCallbackFilter(filter);
OrderService proxy = (OrderService) enhancer.create();
|
CGLIB 的限制
1. 不能代理 final class
1 2 3 4 5 6
| public final class ImmutableService { public void process() { } }
|
因為 CGLIB 要繼承,final class 無法繼承。
2. 不能代理 final method
1 2 3 4 5 6 7 8
| public class UserService { public final void delete(Long id) { } }
|
3. Constructor 的坑
CGLIB 會呼叫父類別的 constructor。如果父類別 constructor 有 side effect,代理時也會執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Service { public Service() { System.out.println("Original constructor called"); initializeConnection(); } }
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service.class); Service proxy = (Service) enhancer.create();
Service proxy = (Service) enhancer.create( new Class[]{}, new Object[]{} );
|
Spring AOP 何時用 CGLIB
Spring 預設選擇策略:
- 有 interface → 用 JDK Proxy
- 沒 interface → 用 CGLIB
可以強制用 CGLIB:
1 2 3 4 5 6 7 8 9
| @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { }
spring: aop: proxy-target-class: true
|
效能對比
| 項目 |
JDK Proxy |
CGLIB |
| 代理 interface 類別 |
快 |
慢(要生成 bytecode) |
| 代理一般類別 |
無法 |
可以 |
| 首次建立 |
快 |
慢(生成 bytecode) |
| 方法呼叫 |
稍慢(reflection) |
快(直接呼叫) |
| 記憶體 |
少 |
多(生成子類別) |
實務上,CGLIB 的初始化成本只付一次,後面方法呼叫效能更好。
踩坑筆記
坑 1:invokeSuper vs invoke
一定用 invokeSuper,用 invoke 會變成無限迴圈。
坑 2:Constructor 重複執行
建立代理時,父類別的 constructor 會執行。如果初始化很重,要特別注意。
坑 3:equals/hashCode 被代理
代理物件的 equals 行為會不同,小心比較。
坑 4:多執行緒
CallbackFilter 要是 thread-safe 的。
最後的話
CGLIB 比 JDK Proxy 複雜,但能代理任何類別,Spring AOP 沒它不行。理解 CGLIB 的原理,你才能更深入了解 Spring 是怎麼實作代理的。下次看到 $$EnhancerByCGLIB$$ 的類別名稱,就知道發生了什麼。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️