Spring AOP 的實現完全建立在動態代理之上。根據目標物件是否實作介面,Spring 預設策略:
java.lang.reflect.Proxy)可透過 @EnableAspectJAutoProxy(proxyTargetClass = true) 強制使用 CGLIB。
public interface HelloWorld {
void sayHello(String name);
}
public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
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;
}
}
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");
}
}
Before invocation: sayHello Hello Mikan After invocation: sayHello
Proxy.newProxyInstance(loader, interfaces, handler)proxyClassCache,命中直接返回;未命中進入 ProxyClassFactory 生成類。java.lang.Proxy,實作所有指定介面。protected Proxy(InvocationHandler h)。Method 物件(含 equals/hashCode/toString)。handler.invoke(this, method, args)。InvocationHandler。com.sun.proxy.$ProxyN),需公開介面。public class SayHello {
public void say() {
System.out.println("hello everyone");
}
}
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;
}
}
public class DoCGLib {
public static void main(String[] args) {
SayHello proxy = (SayHello) new CglibProxy().getProxy(SayHello.class);
proxy.say();
}
}
前置代理 hello everyone 後置代理
| 維度 | JDK 動態代理 | CGLIB |
|---|---|---|
| 前提條件 | 目標類實作一個或多個介面 | 無介面要求,但類與方法非 final |
| 實現原理 | 介面實作 + 委派 InvocationHandler | 字節碼繼承 + FastClass 索引 + MethodInterceptor |
| 代理類關係 | 繼承 Proxy,實作目標介面 | 繼承目標類,覆寫非 final 方法 |
| 生成速度 | 快(輕量級字節碼生成) | 較慢(複雜字節碼操作、FastClass 建立) |
| 呼叫效能 | 反射調用,相對較慢 | FastClass 直接索引,呼叫效能高 |
| 適用場景 | 單例、介面豐富、生成頻繁 | 單例、無介面、效能敏感、調用頻繁 |
| final 方法 | 介面方法隱含非 final | 無法代理 final 方法 |
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 的方法
proxyTargetClass=true)。final 方法,否則 CGLIB 無法攔截。