JDK动态代理仅支持接口,目标类未实现接口时Proxy.newProxyInstance会抛IllegalArgumentException;此时须改用CGLIB等字节码增强方案。

目标类有没有实现接口,直接决定能不能用 Proxy.newProxyInstance
如果目标类没实现任何接口,Proxy.newProxyInstance 会抛 IllegalArgumentException:“class is not an interface”。这是硬性限制,不是配置问题。JDK 动态代理只认接口,不看类本身。
常见踩坑点:有人把 final class UserServiceImpl 和 interface UserService 一起写,以为有接口就能代理——其实能;但一旦删掉接口、只留这个类,JDK 代理立刻失效。这时候别折腾配置,该换 CGLIB。
- 能用 JDK 的典型场景:
OrderService实现了OrderServiceInterface,且你不关心它是不是 final - 必须用 CGLIB 的典型场景:
OrderProcessor是个纯 POJO,没接口,也没父类约束 - Spring 默认行为:检测到接口就走 JDK,否则自动 fallback 到 CGLIB(前提是 classpath 里有
cglib-nodep.jar或 Spring Boot 的 starter 已带)
Enhancer 创建子类时,final 类和 final 方法会直接报错
CGLIB 用的是继承 + 字节码重写,所以遇到 final class 或 final void save(),Enhancer.create() 会抛 IllegalArgumentException:“Cannot subclass final class” 或 “Cannot override final method”。这不是运行时报错,是生成代理类阶段就失败。
对比 JDK:它完全不碰目标类的字节码,只管接口方法签名,所以 final 对它毫无影响。
- 如果你控制不了目标类源码(比如第三方 SDK 里的
final工具类),又必须加 AOP,JDK 代理 + 包装接口是唯一出路 - Spring 中可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)强制启用 CGLIB,但前提是目标类不能含final修饰 - Gradle/Maven 里确认 CGLIB 在 classpath:Spring Boot 2.6+ 默认带
spring-boot-starter-aop,已含cglib-nodep;老项目需手动加cglib依赖
单例对象高频调用时,CGLIB 方法执行更快,但首次创建慢得多
JDK 代理每次方法调用都走 InvocationHandler.invoke + 反射 method.invoke(),开销稳定但略高;CGLIB 生成的子类是普通 Java 方法调用,绕过了反射,执行期更快。
但 CGLIB 首次创建代理要加载原类、解析字节码、生成子类、定义 ClassLoader、注册到 JVM —— 这一整套耗时可能是 JDK 的 5–10 倍。对 prototype bean 或短命对象,这开销不可忽视。
- Web 层 Controller Bean 多为 singleton,适合 CGLIB(一次生成,反复调用)
- DTO 转换器这种临时对象,用 JDK 更轻量
- 压测时注意区分:TPS 看的是平均响应,但首请求延迟(cold start)在 CGLIB 下明显更高
Spring AOP 默认策略下,proxyTargetClass 是开关,不是性能调优项
Spring 的 DefaultAopProxyFactory 判断逻辑很直接:目标类有接口 → 用 JDK;否则 → 用 CGLIB。你改 proxyTargetClass = true,只是强制跳过接口检测,强行走 CGLIB 路径。
这不是“开启高性能模式”,而是在明确知道目标类可被继承的前提下,覆盖默认行为。盲目开启可能触发 final 报错,或让原本能走 JDK 的接口类多一层不必要的子类生成。
- 真正需要改这个配置的场景:你想对某个只有类、没有接口的 service 织入切面,且它不带
final - 不要因为听说“CGLIB 快”就全局设成 true——JDK 的反射开销在现代 JVM(如 ZGC + GraalVM)下已大幅收敛
- 调试时看日志:
Creating JDK dynamic proxy或Creating CGLIB proxy会明确打印,比猜配置更可靠
UserService$$EnhancerBySpringCGLIB$$a1b2c3d4,某些安全沙箱或模块化容器(如 OSGi)会对这类动态类名做拦截,而 JDK 代理生成的类名是标准的 $Proxy123,兼容性更好。

















