剛開始用 Spring AOP 時,以為都是用 JDK Proxy。後來發現為啥有些類別代理不了,還是要靠 CGLIB,才開始研究這東西。CGLIB 現在真的無所不在,有必要搞懂它的原理。

為什麼需要 CGLIB?

JDK Proxy 有個死穴:必須有 interface

1
2
3
4
5
6
7
8
9
10
11
12
13
// JDK Proxy 能代理這個
public interface UserService {
void save(User user);
}

public class UserServiceImpl implements UserService {
public void save(User user) { }
}

// JDK Proxy 代理不了這個 - 沒有 interface
public class OrderService {
public void createOrder(Order order) { }
}

現實中一堆類別沒有 interface,難不成都要改程式?CGLIB 就是為了解決這個問題。

CGLIB 的核心原理

CGLIB 使用 ASM bytecode 框架,在執行時期動態生成目標類別的子類別。然後覆寫父類別的方法,加入攔截邏輯。

簡單說就是:

  1. 讀取目標類別的 bytecode
  2. 用 ASM 框架在記憶體中生成子類別的 bytecode
  3. 把新的 bytecode 載入 JVM
  4. 建立這個子類別的實例
1
2
3
4
5
6
7
8
9
10
11
// CGLIB 會生成類似這樣的子類別
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 {
// obj - 代理物件本身
// method - 被呼叫的方法(Java reflection)
// args - 方法的參數
// proxy - CGLIB 提供的方法代理(用這個呼叫很快)
}

重要:用 proxy.invokeSuper() 而不是 method.invoke()

為什麼?因為 proxy 是直接呼叫父類別的方法,不經過 reflection,效能快很多。

1
2
3
4
5
// 錯誤 - 效能差且容易出問題
method.invoke(obj, args);

// 正確 - CGLIB 最佳化的方式
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);
};

// Filter 決定每個方法用哪個攔截器
CallbackFilter filter = (method) -> {
if (method.getName().startsWith("create")) {
return 0; // 用 authInterceptor
} else if (method.getName().startsWith("find")) {
return 1; // 用 loggingInterceptor
}
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 {  // final class
public void process() { }
}

// CGLIB 會拋 IllegalArgumentException
// Cannot subclass final class ImmutableService

因為 CGLIB 要繼承,final class 無法繼承。

2. 不能代理 final method

1
2
3
4
5
6
7
8
public class UserService {
public final void delete(Long id) { // final method
// ...
}
}

// CGLIB 能建立代理,但 delete() 不會被攔截
// 因為無法覆寫 final method

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(); // "Original constructor called" 會被印出

// 如果要跳過 constructor
Service proxy = (Service) enhancer.create(
new Class[]{}, // 空的 constructor 參數
new Object[]{}
);

Spring AOP 何時用 CGLIB

Spring 預設選擇策略:

  1. 有 interface → 用 JDK Proxy
  2. 沒 interface → 用 CGLIB

可以強制用 CGLIB:

1
2
3
4
5
6
7
8
9
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}

// 或在 application.yml
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$$ 的類別名稱,就知道發生了什麼。