Spring只能够解决单例模式下的setter循环依赖问题,通过三级缓存来解决。对于构造器的循环依赖和非单例模式的循环依赖无法解决。另外还要知道一点,在Spring中bean的实例化、属性赋值、初始化是分开进行的,这也是Spring可以解决循环依赖的一个前提。
下面分析一下Spring是如何通过三级缓存来解决单例模式下setter的循环依赖问题的呢?
缓存 | 用途 |
---|---|
一级缓存singletonObjects | 存放完全初始化好的bean,从这里取出的bean可以直接使用 |
二级缓存earlySingletonObjects | 半成品bean,尚未填充属性,用于解决循环依赖 |
三级缓存SingletonFactories | 存放的是ObjectFactory,可以通过getObject() 执行其中的lambda表达式来取得半成品的bean |
下面拿A中某个field或setter依赖B,且B中某个field或setter依赖A来说一下具体的流程。如有说的不对的地方,欢迎在评论区批评指正。
- 因为Spring中的bean默认都为单例模式,所以在创建A时,首先会执行
getBean()
->doGetBean()
两个方法,看A对象是否已经存在。而此时A对象并没有开始创建,显然结果为null。 - 然后,开始创建A对象。依次执行
createBean()
->doCreateBean()
->createBeanInstance()
三个方法,实例化一个A对象。此时A对象仅仅被实例化,其属性b并没有进行赋值。在三级缓存里面存放键值对<BeanName,getEarlyBeanReference(a)
>。 - A实例化完成后尝试初始化,执行
populateBean()
方法对其进行属性填充,尝试将B对象赋值给A对象的b属性。但是此时,B对象没开始创建,为null。因此,开始执行B对象的创建。 - 创建B对象依旧按照上面的方式进行:
getBean()
->doGetBean()
->createBean()
->doCreateBean()
->createBeanInstance()
,创建出来一个没有进行属性填充的B对象。 - 对创建出的B对象进行属性填充,也就是将A对象赋值给B对象中的a属性,因此需要查找A对象。
- 因为我们并未在一级缓存中存储A对象,所以查找不到。但此时,有一个重要的判断,
isSingletonCurrentlyIncreation(a)
,此时为true,说明A对象正在创建过程中。因此,继续前往二级缓存找到,找不到。继续,直接从三级缓存获取其对应的ObjectFactory
对象,并对其执行getObject()
方法,获取到A的对象。此时获取到的A对象是一个半成品对象,存储到二级缓存,并删除其在三级缓存内的相关内容(因为已经用不到了)。 - 获取到A对象之后,我们就可以对B对象进行属性填充。获取到一个完整的成品B对象。将成品B对象放入一级缓存中,并在二级和三级缓存中删除其相关信息。
- B对象已经成功创建了,此刻回到之前对A对象进行属性填充的断点处,继续完成A对象的属性填充。完成后,A对象也变成了一个完整的成品bean对象。将其放入一级缓存,并清除二三级缓存的相关信息。
- 此刻,A和B两个对象都已经创建成功,循环依赖问题得以解决。
缓存的放置时间与删除时间
三级缓存:createBeanInstance()
之后放入三级缓存,放入的具体方法为addSingletonFactory()
。
二级缓存:第一次通过三级缓存确定需要代理对象还是普通对象的时候放入二级缓存,同时删除三级缓存,具体方法为getSingleton()
。
一级缓存:在生成完整的对象后,存入一级缓存,同时删除二级缓存,具体方法为addSingleton()
。
为什么要用三级缓存?一级缓存可以吗?二级可以吗?
要搞明白这个问题,首先需要了解一下Bean的生命周期。为了方便大家阅读,简单画一个图,从图中可以看到Bean的生命周期大概可划分为实例化→属性填充→初始化。想要更进一步了解Bean生命周期相关内容的同学们移步→Spring中Bean生命周期详解。
首先一级缓存是否可行呢?
考虑如下情况,A和B相互依赖,C依赖A。创建A时,先实例化A,并给A的b属性赋值。发现没有对应的B对象。于是将A的半成品对象存入缓存,并开始创建B对象。此时,C给自己的a属性赋值,从缓存中通过getBean("a")
获取到了仅实例化未初始化的A对象。然后通过a调用A对象内部的属性,比如调用b属性,此时就会出现空指针错误。故:一级缓存不可行。
那么二级缓存是否可行呢?
首先给出结论,在没有AOP的情况下,二级缓存足以解决循环依赖问题。但是当存在AOP的时候,需要用到三级缓存才能够解决循环依赖问题。
可以看到在Bean的初始化前后有两个重要的方法(绿色方块),postProcessBeforeInitialization()
和postProcessAfterInitialization()
。在这个两个方法中会执行当前Bean实现的BeanPostProcessor中的方法,AOP对象的创建就是在这两个方法中实现的。此时关键点就来了,可以看到在Bean的正常生命周期中,AOP对象是在Bean原对象创建之后创建的。
下面我们以A和B相互依赖,且A对象需要AOP的情况为例说明只用二级缓存会出现什么问题。仍然与上面流程相似,实例化A→给A的b属性赋值→B对象不存在→实例化B→将A赋值给B的a属性(a属性对应的是原始A对象而非AOP代理对象)→初始化B→将B赋值给A的b属性。到这里就要注意了,在A对象完成属性填充之后,就会开始创建A对象的AOP代理对象。而最终被使用的,也应该是这个AOP代理对象,而非原始对象。这就会导致B对象的a属性对应的并不是最终版本的A对象。所以在更改Spring源码三级缓存为二级缓存后,会报This means that said other beans do not use the final version of the bean.
的错误,意思是别的Bean对象并没有用这个bean的最终版本。
而新增的第三级缓存做了什么呢?第三级缓存singletonFactories中存储的是<beanName,ObjectFactory>。这个而ObjectFactory对象通过getObject()
调用其内部的lambda表达式()->getEarlyBeanReference()
来获取半成品Bean对象。在getEarlyBeanReference()
这个方法中,Spring会进行判断,看当前对象是否需要生成AOP代理对象,如果需要就生成代理对象并返回,如果不需要就返回原始对象。也就是说,Spring的三级缓存并没有严格的遵守Bean的生命周期,而是把AOP代理对象的创建提前了。
可能上面说的有些混乱,那么仍然以A和B相互依赖,且A对象需要AOP为例来捋一下A、B对象创建的整体流程。实例化A→在三级缓存中存入A对应的<beanName,ObjectFactory>→给A对象的b属性进行赋值→找不到B→实例化B→给B对象的A属性赋值→去一级缓存查找A,找不到→去三级缓存查找A,找到→通过ObjectFactory.getObject()
执行其内部lambda表达式()->getEarlyBeanReference()
→方法内部进行判断发现A对象需要创建AOP代理→为A对象创建AOP代理,并放入二级缓存,删去三级缓存的A相关内容→将A的代理对象赋值给B的a属性→初始化B→将B对象赋值给A对象的b属性→初始化A。此时,B对象内部a属性对应的就是A的AOP代理对象,而非原始对象。
如果还没有听明白的同学可以去听一听源码课→Spring源码解析,个人感觉讲的很好。