spring和cglib不走代理导致空指针报错问题深入分析
问题发现:
在一次需求的过程中,发现原本没有动过的逻辑突然报了空指针的错误,导致程序执行不下去,如下
可以看到logger属性对应的值为null,但是这里为什么logger和tableService是null呢,他不应该是初始化的时候属性就会赋值进去吗?带着这个疑惑,我们先来复现一下这个问题。
复现:
由以上两张图片作为对比,能够发现,图一再调用b方法时,使用了代理,走到了target的实体,可以获取到对应的实体属性。但是在图二,能够发现,调用sayTest方法,并没有使用代理方法,而是一种简单的方法调用,而代理类自身包含的属性都时对应的null。这里稍不注意,就会导致NPE。
这里我思考了一下,如果把 @Transactional 注解注释掉,让这个属于一个正常的bean,而不是cglib,那属性还会时null值吗?
这里实验的结果表明,当这个类不是cglib代理类的时候,调用final方法是能够获取对应的属性,不会产生NPE。
这里就抛出三个问题:
1、Spring什么时候会把bean变成代理bean
2、Spring的代理类型实现方式有两种,一种是JDK的动态代理,一种是cglib的动态代理。他什么时候使用JDK代理,什么时候使用cglib代理?
3、代理类,为什么自身的对应属性是null?而不是初始化时的默认赋值?
问题一:
Spring什么时候会把bean变成代理bean?
这里debug到spring的源码发现,再creatBean的时候,会去递归的注入依赖和实例化、初始化,这时候是没有用代理类进行包装的。这里不考虑spring框架初始化时就将bean变成代理类,当然初始化时是可以重写对应的初始化的方法的,这里不做考虑。
spring在初始化bean之后调用的beanPostProcessors,如下图
不同的beanPostProcessors实现的对应不同的 postProcessAfterInitialization,返回的bean对象要决定于其具体的实现。
这里可以看到,当有AspectJ时,会有对应的postProcessor对当前的bean对象代理。
问题二:
Spring的代理类型实现方式有两种,一种是JDK的动态代理,一种是cglib的动态代理。他什么时候使用JDK代理,什么时候使用cglib代理?
是有cglib代理还是jdk代理,主要的判断逻辑就是这,如上图。
问题三:
代理类(cglib),为什么自身的对应属性是null?而不是初始化时的默认赋值?这也是这次研究的重点!!!这里仔细说说我研究的过程。
首先,能够直接很直观通过debug查看到对应的bean的信息
如上图,可以看到,自己自定义的callback中,里面的targetSource才是我们需要的目标实例,而他的最外层也包含了这些属性,值却是null。
然后我苦苦debug CGLIB的底层代码,还是没有发现创建这一步的原理代码。这时,我的同桌周日红他写了一个cglib的demo代码,并且告诉我,他写的cglib最外层的属性值不是null。首先来看看日红同学写的cglib代码
这里就很纳闷,为什么这样写最外层的属性就有对应的值,而spring动态代理出来的却没有。所有翻阅了spring的源码,发现spring源码使用cglib来生成代理类的class,而不是直接生成代理对象
这里我把代码摘出来,spring生成cglib并且实例化的代码流程如下图
spring生成cglib不是通过对应的实例来生成代理类,而是使用了createClass()来生成class,最后通过SpringObjecesis工具来生成对用的代理对象。但是为什么spring用工具初始化时,最外层属性是null呢?这里我把目光放在了SpringObjecesis工具如果初始化上了,看了SpringObjecesis源码发现
这里可以看到,这里通过反射拿到了 sun.reflect.ReflectionFactory 类,执行了getReflectionFactory方法,获取到对应的ReflectionFactory,并且执行
newConstructorForSerialization。
这里的关键点就是这个!!!reflectionFactory.newConstructorForSerialization,小弟不才,已经看不懂这方法里面具体干了些什么了,所以查阅了下资料:
资料的地址:https://zhuanlan.zhihu.com/p/158978955,大概的意思是说:通过这个方法可以生成没有参数的构造函数,所以在初始化时不用传入构建参数,spring使用这个,大概是为了减少因为构造参数而导致初始化时流程变的更复杂吧。这也是我的个人猜测,有兴趣的小伙伴可以自己查看下相关资料。
总结:简单的来说,就是代理对象,最外层的属性是通过reflectionFactory.newConstructorForSerialization构建出来的,所以他最外层的属性都是null,当我们通过代理对象调用代理方法时,能够通过代理走到对应的callback里面的target的,访问对应的实例。当时当配到无法代理的方法时,如:private、final等方法,是不会生成对应的代理方法,则是默认访问的最外层的属性。当然,如果不是的代理对象,就不会有这个问题!开发的时候避免踩坑
避免踩坑的拓展:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具