JDK 动态代理与 CGLIB 有何区别?Spring 如何选择?
结论先行:JDK 动态代理基于接口:运行时用 Proxy 生成实现同接口的代理类,方法调用统一转发给 InvocationHandler;CGLIB 基于继承:运行时生成目标类的子类并重写非 final 方法。Spring AOP 默认策略是有接口用 JDK 代理、无接口用 CGLIB,而 Spring Boot 2.x 起默认全程使用 CGLIB。
两者对比
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 原理 | 接口 + InvocationHandler 转发 | 生成子类 + 字节码增强(ASM) |
| 前提 | 目标必须有接口 | 类不可 final,方法不可 final/static |
| 创建方式 | Proxy.newProxyInstance | Enhancer.create |
| 额外依赖 | JDK 自带 | 依赖 cglib(Spring 内置) |
| 性能 | 代理类创建快,调用走反射转发 | 创建慢,调用走增强字节码、更快 |
import java.lang.reflect.*;
public class ProxyDemo {
interface Say { void hi(); }
static class Real implements Say {
public void hi() { System.out.println("real hi"); }
}
public static void main(String[] args) {
Real target = new Real();
Say proxy = (Say) Proxy.newProxyInstance(
Real.class.getClassLoader(),
new Class<?>[]{Say.class},
(p, method, a) -> {
System.out.println("before");
Object r = method.invoke(target, a);
System.out.println("after");
return r;
});
proxy.hi();
}
}
常见追问 / 记忆点
- 记忆点:JDK 代理要接口、CGLIB 要可继承;Spring Boot 2+ 默认 CGLIB(proxyTargetClass=true)。
- 追问:类内部 this 调本类方法不会经过代理对象,因此自调用场景切面会失效。
- 追问:Spring 早期优先 JDK 代理,是因为接口是解耦规范,且 JDK 代理类生成开销更小。