【Spring源码系列- IOC】
通过上篇(【实践向】当移除了三级缓存…… )的实践,我们得出的结论是:如果不存在代理对象,二级缓存就可以解决循环依赖性的问题,但是当存在代理对象的时候,二级缓存则无法完全解决循环依赖,需要引入三级缓存
那么、在没有三级缓存的情况下,引入代理后为什么会报错?
由于我们的代码只修改了两处,但前后出现了不同执行结果,所以我们可以将分析重点🔎放在修改的两处代码上(代码与文章【实践向】当移除了三级缓存…… 中的修改后的代码一致,想动手实践下的可以去瞅下,本文较长,这里就不贴叻)
给个“缩略图”吧(˶‾᷄ ⁻̫ ‾᷅˵)下面两张图中红框框内注释掉的为原代码,未注释的为修改后的代码
-
第一处修改的位置
-
第二处修改的位置
流程图
先放个报错的流程图,可以对照这个看后面的Debug步骤
Start^ ^!
ps >>> 自行尝试Bebug的客官需要注意的是我们这里只关注与beanA和beanB这两个bean相关的对象的相关操作,所以如果遇到Variable中的“name”或者“beanName”不是beanA/beanB的对象就直接单击Resume进入下一个断点即可(毕竟Spring不只需要创建这俩Bean)
启动后,获取BeanA
未获取到,开始创建BeanA的实例对象
我们直接到BeanA的实例化完成后,将其半成品BeanA@2289放入二级缓存的位置(本文中的半成品均指只完成实例化未完成初始化的实例对象)
上图可以看到此时半成品BeanA@2289的属性beanB还是null,接着开始进行属性填充,applyPropertyValues()方法中通过 valueResolver.resolveValueIfNecessary()方法解析出beanA的属性beanB并尝试从缓存中获取
显然还是获取不到的,于是开始创建BeanB的实例对象
实例化完成后生成了BeanB的半成品对象BeanB@2416,同样将其放入二级缓存中
然后进行属性填充,解析并尝试获取半成品对象BeanB@2416的属性beanA,此时二级缓存中已经有了BeanA的半成品对象BeanA@2289,于是获取到了对象BeanA@2289
通过调用setPropertyValues()方法将获取到的对象BeanA@2289填充到BeanB@2416对象的属性beanA中(如下图可以看到BeanB@2416的属性beanA已经是对象BeanA@2289了)
继续完成对象BeanB@2416的初始化
初始化过程中先是调用前置处理器
这里会遍历该工厂创建的bean的BeanPostProcessors,最终返回一个经过其所有BeanPostProcessor对象的后置处理器层层包装的对象
然后执行初始化方法,最后会调用后置处理器
与前置处理器逻辑基本一致
但是,有一个特别的BeanPostProcessor
此时会进入到AbstractAutoProxyCreator抽象类的postProcessAfterInitialization()方法中
在wrapIfNecessary()中为BeanB创建代理对象
将创建好的代理对象放入集合proxyTypes中,然后返回
返回到调用后置处理器的方法中,再继续遍历完剩余的后置处理器,对新生成的代理对象进行层层包装,最后将该代理对象返回
返回到初始化方法中
值得注意的,在applyBeanPostProcessorsAfterInitialization()方法的返回值赋值给wrapBean对象前,该对象的值是BeanB@2416
完成赋值后,wrapBean对象的值变成了BeanB@1a6d8329,没错就是刚创建的代理对象
接着完成了将bean对象注册到容器的操作,BeanB的实例对象的创建就完成了
还记得我们为啥么要创建BeanB的实例对象吗?
自问自答:因为在给BeanA的实例对象beanA的beanB属性赋值的时候没有从缓存中获取到BeanB的实例对象,于是我们开始了BeanB的实例对象的创建,所以当BeanB的实例对象创建好后,就会继续回到BeanA对象填充beanB属性的步骤,并将返回的BeanB@1a6d8329对象通过setPropertyValues()方法填充到了的beanB属性中
此时就完成了BeanA@2289对象的属性填充,开始执行初始化方法initializeBean()
根据上面执行的经验,我们直接进入到后置处理器的调用方法applyBeanPostProcessorsAfterInitialization()中
可以看到此时需要遍历的有4个BeanPostProcessor,当遍历到AspectJAwareAdvisorAutoProxyCreator时
进入postProcessAfterInitialization()方法,会发现再次来到了AbstractAutoProxyCreator抽象中
于是相同的流程,进入wrapIfNecessary()方法,一步一步创建BeanA的代理对象
创建完成后,将返回的代理对象BeanA@1c7696c6赋值给exposedObject对象,而这个对象原先的值是BeanA@2289
然后就要开始咱们的终极大判断叻(。・ω・。)ノ
获取缓存中名为beanA的对象并赋值给earlySingletonReference,此时earlySingletonReference是BeanA@2289,exposedObject在刚刚的操作被赋值为BeanA@1c7696c6,因此和bean(BeanA@2289)并不相等,于是程序会继续判断!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName),两个判断都满足,于是进入到else if的代码块中
在这段代码中获取到了beanA的依赖对象有beanB,并将其加入到集合actualDependentBeans中
End= =
因此此时!actualDependentBeans.isEmpty()的值为true,于是进入到了if条件代码块中,嘿嘿、然后就报错了(˶‾᷄ ⁻̫ ‾᷅˵)……
引发的问题
没有三级缓存引发了什么问题?
通过上面的Debug流程和报错信息不难发现,移除了三级缓存使得在容器中存在BeanA对象的实例,一个是已经注入到BeanB的半成品对象BeanA@2289,另一个是执行初始化时生成的代理对象BeanA@1c7696c6,一个单例模式下的容器怎么能容纳两个名为beanA对象,因而报错
简单来说,就是在生成代理对象的时候,发现已经有一个同名的对象存在并被其他对象依赖了
这个问题是在哪里解决的?
回答放前面(体贴下不想看过程的朋友(。・ω・。)ノ)
这个问题自然是在上面测试流程中修改掉的两个方法getSingleton()和addSingletonFactory()中处理解决的
其实无论是否配置了代理,二级缓存中都是存放半成品对象的
三级缓存的存在使得当判断需要生成代理对象时,会从从三级缓存中直接调用工厂对象生成代理对象,并添加进二级缓存中
恢复原先的代码
再次启动,我们直接从BeanA的实例对象的创建开始,可以看到下图中执行到我们一开始修改代码用二级缓存替换三级缓存的位置,此时,二级缓存和三级缓存都还是空的(黄框框),并且已经创建了一个BeanA的半成品对象BeanA@2324
然后我们进入方法addSingletonFactory()中
执行完以后,就将beanA和其lambda表达式添加到了三级缓存中,此时二级缓存依旧是空的
随后将半成品对象BeanA@2324赋值给exposedObject,继续执行下面的逻辑
值得注意的是,这次,这个半成品对象并没有放进二级缓存中,那么意味着在后面创建BeanB的实例对象进行属性注入时,无法从二级缓存中获取到这个半成品对象,那么按照源码的逻辑,会继续尝试从三级缓存中获取,于是,会获取到在addSingletonFactory()方法中添加的beanA的lambda表达式,进一步创建出代理对象,为BeanB的实例对象的beanA属性赋值
我们继续Debug到创建BeanB的实例对象进行属性注入时获取BeanA的步骤
由于此时一级缓存、二级缓存都为空,因此会尝试从三级缓存中获取,此处会通过singletonFactory.getObject()这个回调方法去调用getEarlyBeanReference()方法(原因如下图),进而开始BeanA代理对象的创建
在刚进入这个方法时,可以看到bean对象和exposedObject对象的值是一致的,都是BeanA@2324
当这个方法中的逻辑都执行完成后,bean对象还是BeanA@2324,而exposedObject已经变成了代理对象BeanA@60099951
回调方法执行完后,将生成的代理对象返回赋值给变量singletonObject,接着将这个生成的代理对象添加进二级缓存,并从三级缓存中移除
使用获取到的代理对象完成属性赋值
继续完成BeanB实例对象的初始化,与BeanA的初始化流程一致,调用后置处理器的过程中同样会为BeanB生成代理对象,如下图中再在遍历调用AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization()初始化后置方法前,result=BeanB@2450
遍历结束后,返回的值是新生成的BeanB的代理对象BeanB@20140db9
在获取到完整的BeanB实例对象(代理对象)后,回到BeanA创建对象属性赋值的流程中,将获取到的代理对象BeanB@20140db9赋值给BeanA的beanB属性
继续执行BeanA的实例对象的初始化流程,来到调用后置处理器的步骤(嘿嘿( ̄∇ ̄)没错这里又要创建代理对象叻)
一切都是那么相似,又要调用AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization()初始化后置方法,来获取BeanA的代理对象,不同的是,此时二级缓存(earlySingletonObjects)中已经有了一个之前创建好的BeanA的代理对象BeanA@60099951,所以不会再创建了,直接返回传入的对象BeanA@2324
接下来的判断earlySingletonExposure为true,于是从缓存中获取到BeanA的代理对象,赋值给earlySingletonReference
由于初始化函数返回的exposedObject和bean对象相等,所以开始执行if条件代码块,即将earlySingletonReference的值赋值给exposedObject,并返回exposedObject
于是获取到了BeanA的代理对象BeanA@60099951
此时这个代理对象的属性beanB还是null
而通过beanA.getBeanB()获取的BeanB对象(进入了拦截器的逻辑),其beanA属性也是null(这跟没有引入代理的循环依赖也有区别)
当移除代理的相关配置后
还记得代理是在哪一个步骤替换掉同名的一般对象的吗?
自问自答:getEarlyBeanReference()
而当我们不引入代理时
此时,不会进入红框框内的判断逻辑代码块
返回的exposedObject对象就是传入的bean对象(都是BeanA@1513)
接着会将获取到的BeanA的半成品对象放入二级缓存,并将其工厂方法从三级缓存中移除
接着BeanB继续执行属性赋值、初始化,并在addSingleton()方法中,将生成的完整对象BeanB@1554放入一级缓存,将他的工厂方法从三级缓存中移除
可以看到初始化步骤中调用的后置处理器也从引入代理时的4个,减少到了3个,所以此处不会生成代理对象来覆盖已经生成的一般对象
此时全局只有一个BeanA的实例对象BeanA@1513
自然也没有机会报红框框中的异常(。・ω・。)ノ
一路返回,在addSingleton()方法中,将BeanA@1513也加入了一级缓存,并从二级三级缓存中把同名对象移除
于是一开始进入的getSingleton()方法获取了BeanA的完整的实例对象
一路返回到最初的getBean
于是循环依赖也解决了٩(˃̶͈̀௰˂̶͈́)و
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135370.html