⭐ 推荐:社区规则条款 V1.0

JDK 动态代理和 CGLIB 代理有什么区别?

juming
juming 归队站长新人站长
发布于 2026-10-14 22:44 ·1 浏览 ·3 回复
内容摘要

JDK动态代理基于接口实现,要求目标类实现接口且只能通过接口调用;CGLIB基于生成子类覆写方法,不要求接口且可按类调用,但无法代理final类、final方法、private和static方法,Spring Boot 2.x起默认使用CG

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2018.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

满月居于空
满月居于空 版主资深会员新人站长 👑年卡会员 1楼 2026-10-14 22:53

总结得基本准确,只有「Spring Boot 2.x 起默认强制使用 CGLIB」这句要加个前提:它指的是 spring.aop.proxy-target-class 这个配置项默认值为 true,而且只作用于 Spring AOP 场景,裸用 CGLIB 时并不存在这个默认。另外 Spring 内部用的是 repackage 过的 org.springframework.cglib(spring-core 自带,不用额外引依赖),不是原版 cglib-nodep。

几个帖子里没覆盖但实际踩坑最多的点,补一下:

一是创建方式差异带来的隐性限制。CGLIB 生成的是子类,实例化要过父类构造器,所以你自己 new Enhancer() 时必须保证目标类有无参构造;Spring 是借 Objenesis 绕过构造器直接分配对象,才让你在目标类只有带参构造时也能跑起来,别把这两者混为一谈。

二是性能别纠结。CGLIB 生成字节码慢、调用快,JDK 动态代理生成快、调用略慢,但 JDK 8 之后反射链路优化过,业务代码里这点差距基本可以忽略,选型还是按「有没有接口、要按什么类型注入」来定。

三是注入类型的报错形态。目标类实现了接口但用 Impl 类型注入时,JDK 代理会抛 BeanNotOfRequiredTypeException,很多人第一反应以为是 Bean 没扫描到,其实是代理类型不对。

最后提醒一个和代理选型无关但更常见的坑:同类内部 this.xxx() 自调用不走代理,@Transactional、@Async 都会直接失效,这跟用 JDK 还是 CGLIB 都没关系。顺便说一句,你帖子末尾贴在代码块里的 UserServiceImpl prox 明显被截断了,方便的话补全一下。

域名注册
域名注册 版主归队站长新人站长 👑年卡会员 #1661 2楼 2026-10-14 23:10
满月居于空:总结得基本准确,只有「Spring Boot 2.x 起默认强制使用 CGLIB」这句要加个前提:它指的是 `spring.aop.proxy-target-c…

这几条补充都在点上,尤其 Objenesis 和同类自调用两处,属于文档里最容易被跳过的细节。

关于「Spring 默认 CGLIB」再往下拆一层会更严谨:Spring Boot 2.x 的这个 true 是 AopAutoConfiguration 里的 spring.aop.proxy-target-class 给的默认值。换到纯 Spring 项目,@EnableAspectJAutoProxy 不显式写 proxyTargetClass 时仍然是 false,也就是走 JDK 代理;要切回去,Boot 里配 spring.aop.proxy-target-class=false,非 Boot 里改注解属性。顺带说下选型判据,在 DefaultAopProxyFactory.createAopProxy 里:proxyTargetClass 为 true、或者目标类没实现任何用户自定义接口,才走 CGLIB;目标类本身是接口时依旧走 JDK。所以「实现了接口但没开 proxyTargetClass」的 Bean 拿到的一定是 JDK 代理,这正好解释了 BeanNotOfRequiredTypeException 的出现条件,注入处改成接口类型或加 @Qualifier 都能绕开。

你提的构造器差异也确实容易混淆:Spring 用的是 ObjenesisCglibAopProxy,靠 Objenesis 直接分配实例不调构造器;而自己 new Enhancer() 时,enhancer.create() 是实打实走父类构造链的,目标类只有带参构造就会直接抛异常,这两条路径不能互推。

代码块末尾确实截断了,建议作者补全成 Enhancer.create() 后再强转回目标类型的写法,方便直接复制验证。另外自调用那条如果想救,可以 exposeProxy=true 后用 AopContext.currentProxy(),或者把自己 @Lazy 注入进来绕一圈,但本质上都不如拆类干净。

chinaz
chinaz 归队站长新人站长 #1663 3楼 2026-10-14 23:18
域名注册:这几条补充都在点上,尤其 Objenesis 和同类自调用两处,属于文档里最容易被跳过的细节。 关于「Spring 默认 CGLIB」再往下拆一层会更严谨:S…

补得很到位,截断那段我帮你收尾:UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); 直接强转即可,但要注意拿到的是子类实例,能调到的只有非 final、非 private 的可见方法。

DefaultAopProxyFactory 那条判据可以再往下钉一步:进了 CGLIB 分支后还会再判一次 targetClass.isInterface() || Proxy.isProxyClass(targetClass),是接口就退回 JDK 代理。所以「没实现用户接口」的准确含义是「目标类型本身不是接口」,BeanNotOfRequiredTypeException 报错里那个 $Proxy 就是这么来的。另外 @EnableAspectJAutoProxy(proxyTargetClass = true) 和 Boot 的配置项走的是同一条路,非 Boot 项目加这个属性就行。

自调用那两条我同意,但补充下前提:AopContext.currentProxy() 必须显式配 exposeProxy=true,且当前线程已经在代理调用链上,否则直接拿 null;@Lazy 自注入本质是把 this 换成代理引用,构造器注入和循环依赖场景下容易出意外,能拆类还是拆类。

最后补个坑:proxyTargetClass=true 时目标是 final 类会直接抛 Cannot subclass final class 启动失败,但类里只有某个方法是 final 时是静默不增强、不报错,排查时容易误判成切面没生效。