Spring中的循环依赖
在之前的文章中,分析了AbstractBeanFactory#getBean()获取bean时,会调用到AbstractBeanFactory#doGetBean() ,在该方法中调用到AbstractAutowireCapableBeanFactory#createBean(),最终是调用了AbstractAutowireCapableBeanFactory#doCreateBean,这里面会清晰的看到创建Bean的几个过程:
在调用AbstractBeanFactory#doGetBean()方法中会调用DefaultSingletonBeanRegistry#getSingleton()来看这个beanName是否已经加载并被DefaultSingletonBeanRegistry缓存了。
DefaultSingletonBeanRegistry是共享bean实例的通用注册表,里面会缓存bean,主要关注类中的下面几个filed:
1 | /** 缓存singleton对象 */ |
来看getSingleton(beanName)方法:
1 |
|
首先从singletonObjects中尝试获取,如果获取不到并且对象在创建中(singletonsCurrentlyInCreation中包含),则尝试从earlySingletonObjects中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则调用singletonFactory.getObject()获取。
如果getSingleton(beanName)获取不到对应的bean,AbstractBeanFactory就开始创建:
1 | // Create bean instance. |
1 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { |
这里的getSingleton方法大致流程是,从singletonObjects中获取名称为beanName的对象;如果为空,则beforeSingletonCreation()中先检查DefaultSingletonBeanRegistry 类中的inCreationCheckExclusions是否不包含该beanName,并在singletonsCurrentlyInCreation中添加该beanName,失败则抛出BeanCurrentlyInCreationException。
然后调用singletonFactory.getObject()即createBean()方法来创建该bean,创建成功后会从singletonsCurrentlyInCreation删除该beanName,并将beanName对应的对象添加到singletonObjects中:
1 | protected void addSingleton(String beanName, Object singletonObject) { |
注意,在AbstractAutowireCapableBeanFactory#doCreateBean()方法中,createBeanInstance()执行完成之后,此时单例对象此时已经被创建出来了,但是populateBean、initializeBean还没执行,但Spring此时也会将这个对象提前曝光出来:
1 | protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) |
1 | protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
Spring这么设计有什么好处呢?来分析一下,假设Abean的某个field或者setter依赖了Bbean的实例对象,同时Bbean的某个field或者setter也依赖了Abean的实例对象,在这种循环依赖的情况下,会出现什么情况。
Abean首先完成了初始化的第一步(createBeanInstance),并且将自己提前曝光到singletonFactories中,在进行初始化的第二步时,发现自己依赖对象Bbean,此时就尝试去getBean(BbeanName),发现Bbean还没有被创建,于是开始创建。Bbean在初始化第一步的时候发现自己依赖了对象Abean,于是尝试getBean(AbeanName),会先调用getSingleton(beanName)方法,在该方法中会在singletonFactories中拿到Abean对象,Bbean拿到Abean对象后顺利完全初始化之后将自己放入到singletonObjects中。此时返回Abean的初始化过程,Abean此时能拿到Bbean的对象顺利完成自己的初始化,最终Abean也完成了初始化,放入到singletonObjects中。整个过程没有问题。
但是,如果是Abean的构造函数中依赖了Bbean的实例对象,同时Bbean的构造函数也依赖了Abean的实例对象,在这种循环依赖的情况下,Bbean在初始化时发现自己依赖了对象Abean,于是尝试getBean(AbeanName),调用getSingleton(beanName)方法发现没有,于是开始创建,而在singletonsCurrentlyInCreation已经有了Abean,于是抛出**BeanCurrentlyInCreationException**异常。
以上是对singleton的bean分析,如果是prototype呢?由于Spring容器不进行缓存prototype对象,因此无法提前暴露一个创建中的Bean。所以prototype的循环依赖是存在问题的。
总结:简单来说就是对象通过构造函数初始化之后就暴露到容器中,这样就不会存在循环初始化对象的情况了。
构造循环依赖:这种情况,类A的构造方法中传入类B,同时类B的构造方法中传入类A,这种循环依赖会报错,Spring无法解决。
字段循环依赖:这种循环依赖,Spirng可以解决并且不会报错。
Setter注入:Setter注入和属性注入一致,可以解决。
原型循环依赖:因为原型的Bean只有在使用的时候才会去创建,而且每次都是创建一个新的,因此无法解决。
为什么有三级缓存不是二级缓存?
二级缓存可以解决这个问题吗?其实二级缓存是可以解决这个问题的,但是前面说了三级缓存的目的是将返回早期引用对象这个动作给用于预留一个扩展接口,在三级缓存中获取Bean调用的getEarlyBeanReference会回调SmartInstinationBeanPostProcessor这个接口的方法,开发者可以在这个阶段对返回的Bean对象做修改,如果AB循环依赖,B就会调用getEarlyBeanReference获取A,从而B会从三级缓存得到一个扩展后的A,并移动到二级缓存。
换个角度假如只有2级缓存,那么用户扩展的动作就要放到二级缓存来做,这样每一次来获取的时候如果一级缓存没有都需要在二级缓存执行一次singletonFactory.getObject()触发一下获取Bean,这样这个方法很可能会被执行多次,每次都会创建一个新的对象,由此肯定会引起问题,引入了三级缓存singletonFactory.getObject()只会在第一次获取的时候,前两级缓存都不存在才会触发一次,并立刻将自己放到二级缓存,并清空三级缓存,后面每一次获取的都是这次创建的这个对象。
如果不需要扩展,放进去的早期引用就是不完整的Bean,不考虑给其他后置处理器扩展,这样使用2级缓存是可以的。A直接将自己放入二级缓存,循环依赖的时候B直接从二级缓存获取A的早期引用保证B初始化成功再将自己加到一级缓存,然后A初始化成功后添加到一级缓存,但是这样就不能扩展了,因为这个扩展点很重要,在AOP的AnnotationAwareAspectJAutoProxyCreator就通过这个扩展点来保证代理对象的返回和代理对象的循环依赖问题解决(保证循环依赖的时候,返回的对象也是代理对象)
综上来看三级缓存的目的是预留一共扩展接口,而这个接口和AOP有关。
Spring到底解决了哪种情况下的循环依赖
AOP就是通过一个BeanPostProcessor来实现的,这个BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。
一般过程就是:A类—>生成一个普通对象–>属性注入–>基于切面生成一个代理对象–>把代理对象放入singletonObjects单例池中。
而AOP可以说是Spring中除开IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功能想要并存,Spring需要特殊处理。
如何处理的,就是利用了第三级缓存singletonFactories。
首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。这个ObjectFactory是一个函数式接口,所以支持Lambda表达式:**() -> getEarlyBeanReference(beanName, mbd, bean)**
上面的Lambda表达式就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,而该方法如下:
1 | protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { |
该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter,它的实现如下:
1 | // InstantiationAwareBeanPostProcessorAdapter |
1 | // AbstractAutoProxyCreator |
所以很明显,在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。上文提到的AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator。
那么getEarlyBeanReference方法到底在干什么?
首先得到一个cachekey,cachekey就是beanName。
然后把beanName和bean(这是原始对象)存入earlyProxyReferences中
调用wrapIfNecessary进行AOP,得到一个代理对象。
那么,什么时候会调用getEarlyBeanReference方法呢?回到循环依赖的场景中

左边文字:这个ObjectFactory就是上文说的labmda表达式,中间有getEarlyBeanReference方法,注意存入singletonFactories时并不会执行lambda表达式,也就是不会执行getEarlyBeanReference方法
右边文字:从singletonFactories根据beanName得到一个ObjectFactory,然后执行ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象经过AOP之后的代理对象,然后把该代理对象放入earlySingletonObjects中,注意此时并没有把代理对象放入singletonObjects中,那什么时候放入到singletonObjects中呢?
我们这个时候得来理解一下earlySingletonObjects的作用,此时,我们只得到了A原始对象的代理对象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入singletonObjects中,所以只能把代理对象放入earlySingletonObjects,假设现在有其他对象依赖了A,那么则可以从earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代理对象。
当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了,那么怎么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断当前beanName是否在earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。
对于A而言,进行了AOP的判断后,以及BeanPostProcessor的执行之后,就需要把A对应的对象放入singletonObjects中了,但是我们知道,应该是要A的代理对象放入singletonObjects中,所以此时需要从earlySingletonObjects中得到代理对象,然后入singletonObjects中。
总结
至此,总结一下三级缓存:
- singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
- earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
- singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
- 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。