JDK动态代理只能代理接口而非普通类,因Proxy.newProxyInstance()生成的代理类是实现接口而非继承类,传入非接口类型如ArrayList会抛IllegalArgumentException;无接口时需改用CGLIB代理。

为什么 JDK 动态代理不能代理普通类
因为 Proxy.newProxyInstance() 底层只支持接口,它生成的代理类是“实现接口”的,不是“继承类”的。你传一个 ArrayList 这种非接口类型进去,会直接抛 IllegalArgumentException: class java.util.ArrayList is not an interface。
常见错误现象:明明写了代理逻辑,运行时报错说“class is not an interface”,但没意识到目标对象根本没实现任何接口。
- 必须确保被代理对象至少实现一个接口,哪怕空接口也行(比如定义个
MarkerInterface) - 如果目标类没接口,只能换 CGLIB;但要注意 CGLIB 不能代理
final类或final方法 - JDK 代理生成的类名形如
$Proxy0,CGLIB 是类似Service$$EnhancerByCGLIB$$a1b2c3d4
CGLIB 的 MethodInterceptor 和 JDK 的 InvocationHandler 怎么选参数
两者核心都是拦截方法调用,但入参结构不同,容易传错导致空指针或无限递归。
使用场景:你想在方法执行前后加日志、权限校验、事务控制——这时得靠它们把原始调用“包一层”。
立即学习“Java免费学习笔记(深入)”;
- JDK 的
invoke(Object proxy, Method method, Object[] args):第三个参数是Object[],别漏了args,否则调用原方法时参数全丢 - CGLIB 的
intercept(Object obj, Method method, Object[] args, MethodProxy proxy):第四个参数MethodProxy很关键,应该用它调用proxy.invokeSuper(obj, args),而不是用method.invoke(obj, args)——后者会绕过 CGLIB 的优化,还可能触发重复代理 - 两者都需注意:不要在拦截器里直接调用被代理对象自身的方法(比如
this.doSomething()),这会跳过代理逻辑
Spring AOP 默认用哪个?什么时候会切到 CGLIB
Spring 默认优先用 JDK 动态代理,只有明确需要代理类(比如没有接口的 @Service 类)且启用了 CGLIB 支持时,才 fallback 到 CGLIB。
性能影响:JDK 代理启动快、内存占用小;CGLIB 启动慢(要生成字节码)、运行时略快(直接调用,无反射开销),但对 final 方法无效。
- Spring Boot 2.6+ 默认关闭 CGLIB 代理,除非你在
@EnableAspectJAutoProxy(proxyTargetClass = true)显式开启 - 如果你的切面作用在没有接口的类上,又没开
proxyTargetClass = true,那 AOP 根本不生效——连日志都不会打,静默失败 - 检查是否生效:打印代理对象的类名,如果是
$Proxy开头就是 JDK,$$Enhancer就是 CGLIB
代理对象序列化失败怎么办
代理对象(尤其是 CGLIB 生成的)默认不可序列化,一放进 Redis 或跨 JVM 传参就报 NotSerializableException。
原因很简单:JDK 代理类实现了 Serializable,但 CGLIB 默认不实现;而且即使实现了,内部持有的 MethodInterceptor 如果引用了非序列化对象(比如 Spring 上下文、数据库连接),照样炸。
- 强制让 CGLIB 实现
Serializable:用Enhancer.setInterfaces(new Class[]{Serializable.class}),但只是表层有效 - 真正安全的做法:别序列化代理对象本身,只序列化原始目标对象 + 代理逻辑分离(比如用注解标记,运行时重新织入)
- Spring 中更稳妥的是避免在远程调用链路中依赖代理对象行为,改用显式服务门面(Facade)封装
最常被忽略的一点:你以为加了 implements Serializable 就万事大吉,结果反序列化后拦截器丢了,AOP 彻底失效——代理对象不是“有接口就能串行化”的活物,它是运行时构造的脆性结构。











