CGLIB能代理没有接口的类,因其基于继承机制:通过Enhancer动态生成目标类的非final子类,重写其非final方法,并插入MethodInterceptor拦截逻辑;调用时使用MethodProxy.invokeSuper直接执行父类原方法,避免反射,从而绕过JDK代理对接口的依赖。

为什么 CGLIB 能代理没有接口的类
CGLIB 不依赖接口,而是通过 Enhancer 在运行时生成目标类的子类,重写所有非 final 方法,并在方法体中插入对 MethodInterceptor.intercept() 的调用。这意味着只要类不是 final,且方法不是 final 或 static,就能被拦截。
它不走反射调用目标方法(像 JDK 代理那样),而是用 MethodProxy.invokeSuper() 直接跳转到父类原方法,性能更高,也绕开了反射的访问控制检查。
- 必须有无参构造器(否则
Enhancer创建子类实例会抛IllegalArgumentException) -
final class、final method、private method、static method全部无法代理 —— 这不是 bug,是字节码层面限制 - 若目标类构造器有参数或抛异常,需配合
Enhancer.setCallbackFilter()和自定义CallbackFilter控制代理逻辑范围
如何写出一个可用的 MethodInterceptor 实现
核心是正确使用 MethodProxy,而不是直接反射调用 method.invoke()。后者会绕过 CGLIB 生成的子类逻辑,导致拦截失效或栈溢出。
典型错误写法:method.invoke(obj, args) —— 这会重新触发子类中被重写的方法,形成无限递归。
- 务必用
proxy.invokeSuper(obj, args)调用原始方法 -
obj是子类实例,proxy是 CGLIB 为该方法生成的委托句柄,二者必须匹配 - 如果需要获取目标类真实类型(比如做泛型判断),用
obj.getClass().getSuperclass(),而非obj.getClass() - 不要在
intercept()中缓存proxy或method引用作跨请求复用 —— 它们是线程不安全的,且与具体代理实例绑定
Enhancer 配置的关键参数和陷阱
Enhancer 是 CGLIB 的入口类,但默认行为容易踩坑:比如没设 setSuperclass() 会报空指针,没设 setCallback() 会返回普通实例而非代理对象。
-
enhancer.setSuperclass(UserService.class)—— 必须显式指定,不能传null -
enhancer.setCallback(new UserServiceInterceptor())—— 单回调模式;如需不同方法不同逻辑,用setCallbacks()+setCallbackFilter() -
enhancer.setClassLoader(UserService.class.getClassLoader())—— 显式指定类加载器,避免与 Spring 等框架冲突(尤其在模块化环境或 OSGi 下) - 不建议调用
enhancer.setUseCache(true)(默认已开启),除非你确认代理类结构稳定;缓存失效会导致OutOfMemoryError: Metaspace
Spring AOP 自动切换 CGLIB 的边界条件
Spring 并不会“优先”用 CGLIB。它的选择逻辑很明确:只要目标对象实现至少一个接口,就用 JDK 动态代理;只有当目标对象没有任何接口时,才 fallback 到 CGLIB。
这意味着:哪怕你写了 @EnableAspectJAutoProxy(proxyTargetClass = true),如果目标类实现了接口,Spring 仍可能用 JDK 代理 —— 除非你强制关闭接口代理能力。
- 真正触发 CGLIB 的最小条件:
target instanceof Serializable == false且target.getClass().getInterfaces().length == 0 -
proxyTargetClass = true只是“允许” Spring 使用 CGLIB,不是“强制”。是否启用还取决于目标类是否满足无接口条件 - Spring 内部用的是
ObjenesisCglibAopProxy,它包装了Enhancer,但屏蔽了setThreadCallbacks()等底层能力 —— 如需精细控制(如多回调、延迟初始化),得脱离 Spring 自己 newEnhancer
intercept() 里的 obj 类型,或者看到生成的代理类名形如 UserService$$EnhancerByCGLIB$$a1b2c3d4,就说明它已在工作 —— 但 final 修饰符、类加载器隔离、构造器约束这些硬性边界,绕不开,也掩盖不了。

















