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等方法,是不会生成对应的代理方法,则是默认访问的最外层的属性。当然,如果不是的代理对象,就不会有这个问题!开发的时候避免踩坑

避免踩坑的拓展:

 

posted @   5,  阅读(763)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示