15.三级缓存解决循环依赖
🥬 15.三级缓存解决循环依赖
经过之前的编码,spring最核心的IOC和AOP的内容已经基本搭建完成,IOC章节中实现了bean的定义和注册,属性填充,读取配置文件,应用上下文,修改,初始化,销毁,aware感知,FactoryBean
,事件监听。AOP章节中实现了对普通bean进行增强,自动扫描bean的注册,代理对象注入属性的内容。但是到目前为止,一个关键的问题还没有解决,那就是循环依赖,本节中就来解决这个问题,相关的代码我放到了
仓库
中
原因
在bean的创建过程中,需要依赖一些属性,将这些属性填充完整才能完成bean的创建,而这些属性不只是普通属性,有可能在bean的创建过程中,还会依赖一些其他的bean,一旦这些被依赖的bean在创建过程中也依赖当前的bean,就会产生循环依赖的问题,如下图所示一共有三种循环依赖的情况:
但是三种循环依赖的本质上是一样的,都是在创建过程中依赖一个正在创建的bean,这样相互依赖导致bean无法创建成功,本节中来解决这些问题
思路
为了解决循环依赖的问题,spring引入了三级缓存的概念,其中每一级缓存都各司其职从而解决了循环依赖的问题,下面就以第二种A和B创建过程中互相依赖的情况来描述三级缓存如何解决循环依赖:
- 创建A,此时会先创建一个空bean,然后将这个空bean保存到三级缓存中,不管后面是否能用到,之后进行属性填充
- 在属性填充的过程中,需要填充B,此时就会去一级缓存中找对应的B,发现B不存在,再去二级缓存中找,也没找到,再去三级缓存中找,还是没找到,此时新建B
- 创建B的时候也会先创建一个空bean,然后也将这个空bean保存到三级缓存中,之后进行属性填充
- 在属性填充的过程中,需要填充A的属性,此时就会去一级缓存中找对应的A,发现A不存在,再去二级缓存中找,也没找到,再去三级缓存中找,找到之前注册的空A,然后就判断这个A是否需要AOP,需要的话就创建一个代理对象,不需要的话就是普通对象,然后将这个对象放到二级缓存中并填充给B中A属性
- B执行其他的属性填充,其他的工作,然后完成B的创建,此时B中的A还是一个空壳子
- 完成创建之后,将这个B保存到一级缓存中,然后将其从三级缓存中删除
- 之后继续A属性的填充,此时由于三级缓存中提前保存了未实例化完成的A,于是B可以创建完成,A也就可以用这个B进行属性填充,从而完成创建
- A创建完成之后,执行正常的生命周期,此时的A还是一个普通对象,属性填充完成之后,由于B中的A与当前属性填充完的A是同一个地址,所以B中的A属性不再是空。后面就需要判断是否需要进行AOP,如果前面进行了AOP,这里就不需要进行AOP,最后调用
getSingleton
方法得到最终版的结果。在这个方法中,如果提前进行了AOP,直接从二级缓存中就可以得到最终的答案,如果没有提前进行AOP,此时会从三级缓存中拿到最终的对象(普通或者代理)放进一级缓存中
相当于如果出现了循环依赖,提前进行了AOP,那么二级缓存中就会存储尝试AOP之后的A,进行正常的生命周期到达正常的AOP步骤时,不用再AOP,最后直接将二级缓存中的A保存到单例池中即可。如果没有出现循环依赖,最终也会调用getSingleton
方法,然后重新创建一个AOP对象,关于这里的解释可以看下面的描述:
假设bean需要AOP
如果出现循环依赖,利用三级缓存中保存的信息就可以创建出来一个bean代理对象,然后将其保存到二级缓存中,最后保存到单例池中时,会从二级缓存中拿到这个创建出来的bean代理对象,此时由于AOP提前,所以initializeBean
方法中的正常AOP步骤不会执行,这是第一个正常AOP失效的地方
如果没有出现循环依赖,会正常经过bean的生命周期,之后执行AOP的操作,虽然当前bean得到了一个代理对象,但是最后的代码经过了一步:
|
|
这个代码中的getSingleton
会尝试从一级和二级缓存中拿到bean对象,但是由于没有循环依赖,所以拿不到,于是会从三级缓存中重新创建一个代理bean对象,相当于之前经过正常AOP流程创建出来的bean对象失效不会保存到单例池中,这是第二个正常AOP失效的地方,当没有出现循环依赖时,可以看到正常创建出来的bean代理对象和真正保存到单例池中的代理对象是不一样的:
所以即使将initializeBean
方法中的正常AOP流程去掉也不会对当前的项目有什么影响,因为本项目中的正常AOP流程已经失效了,去掉内部的getSingleton
方法可以使得正常的AOP起到效果,但是出现循环依赖后,会提前进行AOP,代理对象被保存到了二级缓存中,如果不调用getSingleton
方法,那么当前的bean就是普通的bean,会出现错误,所以这里宁愿正常的AOP失效也不要造成错误,正常的AOP失效不会对外产生任何影响
这里只是这个项目中的bug,并不是真正spring中存在这个问题
核心点就是一个提前暴露的三级缓存,还有一个getSingleton
方法内部决定bean的获取逻辑,如果出现循环依赖,先从一级缓存中拿,然后从二级缓存中拿,最后才从三级缓存中拿,保证bean的单例性。提前暴露的目的是为了打破循环
核心点总结如下:
- 空bean一创建,三级缓存中就保存了一个
ObjectFactory
对象,可以根据当前创建的空bean来尝试提前AOP,如果当前对象需要AOP,那么就创建代理对象,否则就创建普通对象,这个bean还没有经历正常的生命周期,为的是出现循环依赖时打破这个循环,提前AOP是为了属性注入时注入的是代理对象的地址而不是普通对象的地址(如果需要AOP的话) - 出现循环依赖时,
getSingleton
方法中控制了三级缓存的顺序,从而打破这个循环依赖 - 后期bean创建完成需要保存到单例池中时,为了保持bean的单例性,会从三级缓存中拿bean对象
下面以流程图的形式描述A和B出现循环依赖时怎么创建:
出现了循环依赖就会用第三级缓存打破,用第二级缓存保持单例性,然后创建完成之后,将bean对象最终保存到第一级缓存单例池中
三级缓存的作用
一级缓存
一级缓存存放的是已经初始化好的bean,也就是最终可以对外使用的bean
二级缓存
二级缓存存放的是还没有完全被初始化好的中间对象代理,即已经生成了bean但是这个bean还有部分成员对象还未被注入进来,但这个bean后面又被依赖时就可以拿这个没初始化好的bean来打破循环依赖,而不是再次从三级缓存中创建,保持了bean的单例性
三级缓存
三级缓存存放的是还未初始化完的bean,而这些bean只是早期的简单对象,并不是代理对象。三级缓存是用于解决循环依赖问题,当对象A依赖对象B时,A对象就会被先临时存放在三级缓存中,然后初始化B对象。在这个时间点如果有别的注入对象需要依赖A对象就会从三级缓存中查询,并通过三级缓存的getObject
方法尝试生成代理对象然后将A对象从三级缓存中删除,放入二级缓存。这一级缓存是为了打破循环依赖
类的变化
为了解决循环依赖,对项目中的类进行了一些修改:
DefaultSingletonBeanRegistry
:在这个类中增加三级缓存的容器,并且修改getSingleton
方法的逻辑,在其中控制三级缓存之间的读取流程,并且对外提供向第三级缓存中保存空白普通bean的方法,修改向第一级缓存单例池中添加bean的代码:核心就是
getSingleton
代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public Object getSingleton(String beanName) { //从一级缓存中拿 Object singletonObject = singletonObjects.get(beanName); if (null == singletonObject) { singletonObject = earlySingletonObjects.get(beanName); // 从二级缓存中拿 if (null == singletonObject) { //从三级缓存中拿 //专门用来打破循环依赖 ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中 earlySingletonObjects.put(beanName, singletonObject); singletonFactories.remove(beanName); } } } return singletonObject; }
InstantiationAwareBeanPostProcessor
:这个接口中新增加一个待实现的方法,用来将对象的AOP提前,AOP中进行匹配器的匹配,当前对象不一定创建代理对象:DefaultAdvisorAutoProxyCreator
:类中将创建代理对象的方法封装起来,在两个方法中调用,分别是postProcessAfterInitialization
和getEarlyBeanReference
方法中,第一个方法中调用创建代理对象的方法是因为执行了正常的AOP流程,第二个方法中调用创建代理对象的方法是因为出现了循环依赖,要先将一个空对象创建出来打破循环依赖,第二个方法经过包装最终会在第三级缓存中的getObject
方法中调用。并且一旦当前对象提前经过了AOP,那么后面在正常AOP的触发时机到达时就不用再进行AOP了,为了实现这种动态的判断,将提前AOP的对象都在容器中保存一份,这个容器是earlyProxyReferences
:AbstractAutowireCapableBeanFactory
:在这个类中修改bean的生命周期过程,当一个空bean创建出来之后,就在三级缓存中保存一个lambda
表达式,表达式内部保存的是上面说的getEarlyBeanReference
方法逻辑,也就是说当从三级缓存中获取对象时会调用保存的getEarlyBeanReference
方法,从而将对象的AOP提前,如果当前对象确实需要AOP,那么就会创建代理对象。否则就是普通对象,然后将这个普通对象保存到二级缓存中并返回,也就是说这一步只是为了尝试给对象进行AOP并且在将对象创建完成之后,会将创建完成的代理对象从二级缓存中拿到并保存到单例池中
ObjectFactory
:这是一个函数式接口,当一个空bean创建完成之后,就利用这个接口类型的lambda
表达式保存刚创建出来的空bean信息:
总的来说就是在将单例池一级缓存改成三级缓存,然后在创建对象时就将未创建完成的bean保存到三级缓存中提前暴露,这样提前暴露就可以打循环依赖,并且如果在创建过程中出现循环依赖,就要将针对对象尝试AOP的步骤提前,此时需要记录一下,后期正常AOP的流程就不用执行了,三个缓存都有其单独的作用,单一职责,第三级缓存只是为了打破循环依赖,一旦出现循环依赖,此时就将尝试对bean进行AOP,然后将尝试AOP 之后的对象放入二级缓存并从三级缓存中删除,此时获取到的bean就是尝试AOP但是还没有属性填充完毕的bean,只是一个外壳,有了自己的最终地址,可以临时使用
bean的创建和获取
下面从bean的创建和获取的角度出发讲解如何解决循环依赖的问题,项目中有wife
和husband
两个bean,剩下的bean都是为了实现spring中基础功能所注册的bean,其中wife
和husband
互相依赖,而wife
需要进行AOP
代理:
读取配置文件,
refresh
方法中调用preInstantiateSingletons
方法开始创建wife
的bean对象,创建时会先尝试去缓存中拿:尝试从三级缓存中拿到wife对象,但是初始状态下拿不到
wife
的bean对象,也就是getSingleton
方法会返回null
:此时需要创建
wife
的bean对象,也就是调用createBean
方法,最终会执行doCreateBean
方法,此时只是将xml文件中配置的bean的注册信息扫描完成,但是并没有完成注册:执行
doCreateBean
方法,内部先创建一个空bean,并且将刚创建的空bean保存到第三级缓存中,后期走到从第三级缓存中获取bean对象时会触发getEarlyBeanReference
方法的执行,针对当前bean尝试AOP之后再返回一个搭好的架子:给
wife
填充属性,这里由于wife内部依赖husband
,而husband
内部又依赖wife
,所以会出现循环依赖,一共经历以下几步来解决循环依赖,从而完成对wife
的创建:填充
hubsband
属性,调用getBean
方法尝试从缓存中拿到husband
:getBean
方法中调用doGetBean
方法,内部先调用getSingleton
方法,尝试从三级缓存中拿到husband
对象,由于husband
还没有创建,所以缓存中还没有,所以会调用createBean
方法创建husband
对象,注意此时wife正在创建中:创建
husband
的bean对象,执行doCreateBean
方法,内部先创建一个空bean,并且将创建逻辑保存到第三级缓存中,此时的第三级缓存中保存的有一个没创建完成的wife
和husband
,两个bean都没有经过AOP:对
husband
进行属性填充,husband
内部依赖wife
,而wife
正在创建中,但是这里可以利用第三级缓存中还没有创建完成的提前暴露的wife
先完成husband
的属性填充:getBean
方法中调用doGetBean
方法,内部先调用getSingleton
方法,尝试从三级缓存中拿到wife
对象完成husband
中对wife
的属性填充,此时第三级缓存中保存了wife
没有创建完成的对象,于是可以拿到,这一步会触发getEarlyBeanReference
方法调用
getObject
拿到需要的wife
对象,内部调用保存的lambda
表达式中的getEarlyBeanReference
方法,将AOP的操作提前,主要是调用getEarlyBeanReference
方法完成AOP的工作,这一步的目的是为了给出现循环依赖的bean填充经历了AOP之后的bean,AOP提前才能使得属性填充中的属性地址正确:在
getEarlyBeanReference
方法内部,先将这个提前AOP的对象保存到容器中标记一下,然后调用wrapIfNecessary
完成对wife
的代理对象的创建并返回:从第三级缓存中拿到提前AOP创建出来的
wife
代理对象之后,将其保存到二级缓存中,并将三级缓存中关于wife
的信息删除,此时返回这个提前AOP 的bean,如果创建了代理对象,那么代理对象内部的target
真正被代理对象还没有创建完,属性还没有填充完成,但是这个wife
代理对象用来给husband
做属性填充是足够的,如果没创建代理对象,那么这个普通对象内部的属性还没有填充完成,但是用来做属性填充也是足够的:所以这一步只是为了避免循环依赖中被依赖的属性需要进行AOP,所以出现循环依赖之后,从第三级缓存中获取bean对象就会将当前对象的AOP提前,没有出现AOP时,第三级缓存是没有作用的
husband
通过getBean
方法得到一个wife
的代理对象之后进行属性填充,可以看到husband
的wife
属性已经填充好了,但是其wife
内部的属性还没有填充完成,只是一个架子,但是这足够解决循环依赖了:完成对
husband
的创建,此时已经打破了循环依赖,完成了对husband
的创建,也就是说,wife
所依赖的husband
创建出来了,此时将husband
保存到单例池中,也就是一级缓存中,并且将husband
保存到剩下两级缓存中的内容删除,这些操作都是为了保持bean的单例性:至此完成
husband
的创建,然后返回刚创建的husband
wife
调用getBean
方法经历上面的10步就拿到了创建成功的husband
对象,husband
内部依赖的wife
此时还没有创建完成,此时就开始wife
的属性填充,也就是回到最开始wife
属性填充到husband
的步骤:属性填充完毕之后,
wife
的创建也结束了,由于xml文件中配置了切面,标记要对wife
进行增强,所以上面husband
获取wife
对象时获取到的是代理对象,当wife
创建成功之后,因为AOP提前了,所以此时的wife
是一个普通对象,这个普通对象在提前AOP时保存到了代理对象中的target中,而真正的wife
经过了AOP增强,需要将代理对象保存到单例池中,所以下一步就是将真正需要保存的bean拿出来保存到单例池中:在创建
husband
对象时,创建的wife
代理对象保存到了二级缓存中,所以此时就调用getSingleton
方法从二级缓存中拿到真实的代理对象,然后将其保存到单例池中:完成对
wife
的创建后,husband
内部的wife
也填充完成,此时wife
和husband
都创建完毕,执行自己的业务逻辑,可以发现出现循环依赖的两个bean都创建成功,并且还执行了AOP的增强:
经过上面的步骤,循环依赖被完美的解决,但是需要注意的是,上面的wife
需要进行AOP,所以创建的是代理对象,如果wife
不需要AOP,那么上面的第八步中调用getSingleton
拿到的exposedBean
和bean
是一样的,都是普通的bean。如果wife
需要AOP,但是wife
没有循环依赖的问题,那么就会执行正常的AOP流程,不会执行提前AOP,最后调用getSingleton
尝试拿到AOP对象时拿到的是一个新的代理对象:
也就是说没有出现循环依赖时,经过正常AOP创建出来的代理对象失效,没有什么作用,最终保存到单例池中供外部使用的代理对象是getSingleton
方法中拿到的新代理对象,但是只是正常的AOP流程失效,并不会对spring对外提供的功能产生影响
总结
经历上面的步骤之后,完美的解决了循环依赖的问题,主要是将使用三级缓存将出现循环依赖的bean提前暴露,并且保持其单例性,打破循环依赖之后,bean就可以正常创建,只是将bean所依赖的bean(普通对象或者代理对象)的地址提前拿到,完成一个bean的创建之后,另外一个bean的创建也可以完成,之后由于地址相同,内容同步更新,最后就可以完成所有bean的创建
主要是第三级缓存保存了一个还没有经过属性填充和AOP的普通空bean,后期一旦出现循环依赖,就从第三级缓存中拿到这个普通空bean并尝试AOP,这一步是为了防止属性填充的是普通bean,之后这个普通bean还需要AOP,从而出现属性填充的bean和最终生成的bean不一致的情况,所以将AOP提前,依赖关系填充好后可以打破循环依赖,后期再慢慢的填充属性即可