代理模式為目標對象提供訪問控制層,是 AOP(面向切面編程)的核心實現。Java 靜態代理(實為動態代理)主要有 JDK 原生代理與 CGLIB 字節碼代理兩大流派,本文深度對比原理、代碼實現、優劣差異。
UserServiceImpl)Proxy.newProxyInstance(ClassLoader, Interface[], InvocationHandler) 生成實現接口的匿名類InvocationHandler.invoke(proxy, method, args) 統一攔截所有方法調用// 1. 業務接口
public interface UserService {
String getName(int id);
Integer getAge(int id);
}
// 2. 目標實現
public class UserServiceImpl implements UserService {
public String getName(int id) {
System.out.println("------getName------"); return "cat";
}
public Integer getAge(int id) {
System.out.println("------getAge------"); return 10;
}
}
// 3. 處理器
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getName".equals(method.getName())) {
System.out.println("------before " + method.getName() + "------");
Object result = method.invoke(target, args);
System.out.println("------after " + method.getName() + "------");
return result;
}
return method.invoke(target, args); // 其他方法直接透傳
}
}
// 4. 測試
public class RunJDK {
public static void main(String[] args) {
UserService proxy = (UserService) new MyInvocationHandler().bind(new UserServiceImpl());
System.out.println(proxy.getName(1)); // 輸出前置/後置增強
System.out.println(proxy.getAge(1)); // 無增強
}
}
------before getName------
------getName------
------after getName------
cat
------getAge------
10
只能代理實現接口的類。無接口類無法使用 JDK 代理。
Enhancer(增強器)、MethodInterceptor(攔截器)、MethodProxy(快速調用)public class CGLIBProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 設置父類
enhancer.setCallback(this); // 設置攔截器
return enhancer.create(); // 生成代理實例
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("++++++before " + proxy.getSuperName() + "++++++");
Object result = proxy.invokeSuper(obj, args); // 調用父類方法
System.out.println("++++++after " + proxy.getSuperName() + "++++++");
return result;
}
}
// 測試
public class RunCGLIB {
public static void main(String[] args) {
UserService proxy = (UserService) new CGLIBProxy().getInstance(new UserServiceImpl());
proxy.getName(1);
proxy.getAge(1);
}
}
++++++before CGLIB$getName$0++++++
getName
------getName------
++++++after CGLIB$getName$0++++++
++++++before CGLIB$getAge$1++++++
getAge
------getAge------
++++++after CGLIB$getAge$1++++++
final 類/方法(無法繼承/重寫)spring-core 中的 CGLIB 重打包版)| 維度 | JDK 動態代理 | CGLIB 代理 |
|---|---|---|
| 前提條件 | 目標實現接口 | 目標非 final 類 |
| 生成機制 | 實現接口(組合) | 繼承目標類(繼承) |
| 核心 API | Proxy, InvocationHandler | Enhancer, MethodInterceptor |
| 調用方式 | 反射 Method.invoke | FastClass 機制/MethodProxy |
| 性能(JDK8+) | 優化後極快,差距微小 | 傳統優勢,現在差距可忽略 |
| 代理類型 | 接口代理 | 類代理 |
| Spring 默認策略 | 有接口用 JDK,無接口用 CGLIB | proxy-target-class=true 強制 CGLIB |
JDK 代理標準、輕量、無依賴,優先選擇;CGLIB 補足無接口場景,性能略優但引入依賴。現代 Spring Boot 2.x+ 默認 proxy-target-class=false(有接口用 JDK),理解兩者差異可在 AOP 不生效、final 方法無法增強、序列化報錯等場景快速定位根因。