CGLIB生成类名含$$EnhancerByCGLIB$$是其默认命名策略,用于标识继承式动态代理并确保类名唯一;JDK代理则用$ProxyN格式,仅支持接口代理。

为什么 CGLIB 生成的类名里总带 $$EnhancerByCGLIB$$
这是 CGLIB 在运行时通过字节码操作直接生成子类的明确标识。它不是随便加的后缀,而是 Enhancer 类默认使用的命名策略 —— 每次调用 create() 都会拼上这个字符串和随机 hash 值,确保类名唯一、避免重复定义导致 LinkageError。
对比 JDK 动态代理:它用 ProxyGenerator.generateProxyClass() 生成的是实现接口的代理类,类名格式是 $Proxy0、$Proxy1,不继承原类,也不需要改类名来规避父子冲突。
- 如果你看到
MyService$$EnhancerByCGLIB$$a1b2c3d4,说明 CGLIB 正在对MyService做继承式增强 - JDK 代理永远无法代理没有接口的类;CGLIB 可以,但要求目标类不能是
final,方法也不能是final或private - Spring 默认优先用 JDK 代理(有接口时),没接口才 fallback 到 CGLIB —— 这个逻辑藏在
ProxyFactory.getProxy()里
MethodInterceptor 和 InvocationHandler 的拦截入口差异
两者都是“怎么把调用拦下来”的钩子,但触发时机和可操作范围不同:
-
InvocationHandler.invoke()只能拿到接口方法调用,参数是Method、Object[] args和Object proxy;无法访问目标对象本身(因为 JDK 代理不持有 target 引用,得自己传) -
MethodInterceptor.intercept()多一个MethodProxy参数,它缓存了原方法和子类桥接方法的调用句柄,还自带proxy.invokeSuper()—— 这是 CGLIB 能绕过代理链、直接调用父类原始逻辑的关键 - 如果你在
intercept()里漏掉methodProxy.invokeSuper(obj, args),就等于完全截断了原方法执行,而不是“增强”
字节码层面:CGLIB 用 ASM 直接写子类,JDK 用 sun.misc.ProxyGenerator
JDK 的代理类生成是黑盒,底层调用 ProxyGenerator.generateProxyClass(),输出的是标准 Java 字节码(符合 JVM 规范),但你没法干预生成过程 —— 比如不能给代理类加字段、不能改构造器逻辑。
CGLIB 底层用的是 ASM 库,逐指令构建类文件:
- 它会复制目标类的非
final方法字节码,再在开头/结尾插入拦截逻辑(类似onBefore()/onAfter()) - 构造器会被重写,第一行强制调用父类构造器,并把
Callback实例塞进字段(比如this.CGLIB$CALLBACK_0) - 所以 CGLIB 代理类体积更大、加载更慢,且对某些安全限制严格的环境(如部分 Android 版本、GraalVM native image)可能直接失败
遇到 java.lang.IllegalArgumentException: Cannot subclass final class 怎么办
这是 CGLIB 最典型的报错,说明你试图代理一个 final 类,或用了 final 方法 —— 它根本没法继承,字节码生成直接失败。
- 检查报错栈里的类名,确认它是否被
final修饰;常见于工具类(如StringUtils)、枚举、或 Lombok 的@UtilityClass生成类 - 如果是 Spring 环境,别硬扛:用
@EnableAspectJAutoProxy(proxyTargetClass = false)强制走 JDK 代理(前提是目标类实现了接口) - 实在没接口又必须代理?只能换方案:用字节码织入(如 AspectJ compile-time weaving),或者把 final 去掉(如果代码可控)
- 注意:Java 17+ 的密封类(
sealed)也会导致同样问题,CGLIB 不识别该关键字,仍按 final 处理
真正麻烦的不是选哪个代理,而是混合场景下两种机制共存时,target 对象的类型判断、AOP 切点匹配、事务传播行为都可能出人意料 —— 尤其当某个 bean 同时被 JDK 和 CGLIB 代理过两次的时候。










