spring和cglib不走代理导致空指针报错问题深入分析

文章探讨了在Spring框架中遇到的空指针异常,涉及Spring何时将bean变为代理bean,以及JDK和cglib动态代理的选择逻辑。重点分析了代理类中属性为何在某些情况下为null的问题,揭示了Spring使用`ReflectionFactory.newConstructorForSerialization`的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题发现:

在一次需求的过程中,发现原本没有动过的逻辑突然报了空指针的错误,导致程序执行不下去,如下

可以看到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,小弟不才,已经看不懂这方法里面具体干了些什么了,所以查阅了下资料:

资料的地址:reflectionFactory.newConstructorForSerialization原理 - 知乎,大概的意思是说:通过这个方法可以生成没有参数的构造函数,所以在初始化时不用传入构建参数,spring使用这个,大概是为了减少因为构造参数而导致初始化时流程变的更复杂吧。这也是我的个人猜测,有兴趣的小伙伴可以自己查看下相关资料。

总结:简单的来说,就是代理对象,最外层的属性是通过reflectionFactory.newConstructorForSerialization构建出来的,所以他最外层的属性都是null,当我们通过代理对象调用代理方法时,能够通过代理走到对应的callback里面的target的,访问对应的实例。当时当配到无法代理的方法时,如:private、final等方法,是不会生成对应的代理方法,则是默认访问的最外层的属性。当然,如果不是的代理对象,就不会有这个问题!开发的时候避免踩坑

避免踩坑的拓展:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值