1、什么是循环依赖?

比如有两个对象,A依赖了B,B又依赖了A:

  1. class A{
  2. public B b;
  3. }
  4. class B{
  5. public A a;
  6. }

那么循环依赖是个问题吗?
如果不用Spring框架,那这不是个问题,以为对象之间的相互引用是很正常的,比如:

  1. class Test{
  2. A a = new A();
  3. B b = new B();
  4. a.b = b;
  5. b.a = a;
  6. }

这样,A和B就依赖上了。
但是,Spring框架里,一个对象并不是简单new出来就行了,它要经过一系列生命周期,正是因为这一系列的生命周期才会出现循环依赖的问题。在Spring中,出现循环依赖的情景有很多,有的场景Spring自己解决了,有的并没有解决,需要程序员自行解决,下面将详细介绍。

2、Bean生命周期回顾

被Spring托管管理的对象叫Bean,Bean的生成步骤大致如下:
1、Spring扫描class得到BeanDefination。
2、根据BeanDefination生成bean。
3、根据class推断构造方法。
4、根据构造方法反射,实例化得到一个对象(原始对象)。
5、进行属性填充(依赖注入)。
6、如果原始对象的某个方法被AOP了,那么需要根据原始对象生成一个代理对象。
7、把最终生成的代理对象放入单例池,下次需要获取这个bean的时候直接从单例池拿就行了。
注意:Spring Bean的生命周期绝不止上边的步骤,我们这里只列出了几个与循环依赖有关系是的步骤。

3、什么是三级缓存

我们先了解下三级缓存的基本概念,后面再详细分析。
三级缓存是通用的说法,一级缓存为singletonObjects,二级缓存为earlySingletonObjects,三级缓存为singletonObjects。
singletonObjects:缓存了经历了完成bean生命周期的,成熟的bean对象。
earlySingletonObjects:缓存未经过完整生命周期的bean对象,如果某个bean出现了循环依赖,就会提前把这个不完整的原始对象放入earlySingletonObjects,如果这个对象要进行AOP,那么存放的就是代理对象。总之,放入到二级缓存的就是没有经过完整生命周期的bean对象。
singletonObjects:缓存的是ObjcetFactory,就是一个lambda表达式。每一个bean的生成过程中,经过实例化后得到一个原始对象后,都会基于这个原始对象生成一个Lambda表达式,并缓存到三级缓存。注意,这个lambda表达式可能会用到,也可能用不到。如果某个对象在依赖注入时没有出现循环依赖,那么,就不会用到这个表达式,这个对象会按照正常的bean生命周期步骤创建bean,并缓存到一级缓存。如果这个对象在依赖注入时发生了循环依赖,那么就从三级缓存里拿到这个lambda表达式,并执行lambda表达式得到一个原始对象,并把这个原始对象放入二级缓存(如果当前bean需要AOP,那么得到的会是一个代理对象)。

4、解决循环依赖思路分析

产生循环依赖的原因无非就是,A创建时—->需要B——>B去创建—->需要A,从而产生了循环,如下图所示:
image.png
那么,如何打破这个循环呢?加一个中间人(缓存)就行了。如图所示,当A对象创建时,在实例化后,依赖注入之前,先把A的原始对象放到缓存(提前暴露,只要放到缓存了,其他bean需要时就可以从缓存拿了),放入缓存后,进行依赖注入,这时候发现需要B对象,如果此时B对象不存在,那么需要创建B对象,过程和A一样,先实例化B,然后将B的原始对象放入缓存,然后依赖注入A,此时可以从缓存里拿到A对象(虽然是原始对象),这样B的生命周期就顺利结束了,A的生命周期也可以结束。
image.png
我们思考一下,按照上边的思路,我们只需要一个缓存就行了呀,为啥Spring还设计了三级缓singletonFactories呢?因为spring还有AOP!这是个重点。bean的生命周期里,在初始化后,会进行AOP,我们知道,若一个对象进行了AOP,那么会产生一个代理对象,我们思考一下,如果A对象在依赖注入结束后,并且初始化后,进行了AOP,产生了一个代理对象,那么对于A而言,其实它最终的bean对象是这个代理对象,但是B属性注入的对象,却不是这个对象!这就产生了冲突!
B依赖的A和最终的A Bean,不是同一个对象!!!
如何解决呢?spring的思路是:引入三级缓存singletonFactories
首先,三级缓存singletonFactories存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象后,就会构造一个ObjectFactory放入三级缓存中,这个Objectfactory是一个lambda表达式;() -> getEarlyBeanReference(beanName, mbd, bean),会去执行getEarlyBeanReference方法,该方法源码如下:

  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  2. Object exposedObject = bean;
  3. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  4. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  5. if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
  6. SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
  7. exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
  8. }
  9. }
  10. }
  11. return exposedObject;
  12. }

而该方法又会调用SmartInstantiationAwareBeanPostProcessor接口中的getEarlyBeanReference方法,而在整个spring中,只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。

  1. // AbstractAutoProxyCreator
  2. @Override
  3. public Object getEarlyBeanReference(Object bean, String beanName) {
  4. Object cacheKey = getCacheKey(bean.getClass(), beanName);
  5. this.earlyProxyReferences.put(cacheKey, bean);
  6. return wrapIfNecessary(bean, beanName, cacheKey);
  7. }

看源码我们发现,这个实现方法这么去处理的:首先得到一个cachekey,cachekey就是beanName。 然后把beanName和bean(这是原始对象)存入earlyProxyReferences中 调用wrapIfNecessary进行AOP,得到一个代理对象
好了,到现在我们仅仅了解了这个lambda表达式最终代码会干什么了(生成代理对象),那么,什么时候执行的lambda表达式的代码呢?我们用图解释(点击图片可放大):
image.png
左边黄图文字:这个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中。
至此,整个循环依赖解决完毕。

5、反向分析一下第三级缓存

经过上边的分析,我们知道了,spring解决循环依赖,是引入了二三级缓存,其中三级缓存用来提前AOP。我们思考,如果不引入第三级缓存,怎么解决AOP问题呢???
有两种不成熟的想法列一下:

  • 实例化之后,依赖注入之前:如果是这样,那么对于每个bean而言,都是在依赖注入之前会去进行AOP,这是不符合bean生命周期步骤的设计的。
  • 真正发现某个bean出现了循环依赖时:按现在Spring源码的流程来说,就是getSingleton(String beanName, boolean allowEarlyReference)中,是在这个方法中判断出来了当前获取的这个bean在创建中,就表示获取的这个bean出现了循环依赖,那在这个方法中该如何拿到原始对象呢?更加重要的是,该如何拿到AOP之后的代理对象呢?难道在这个方法中去循环调用BeanPostProcessor的初始化后的方法吗?不是做不到,不太合适,代码太丑。最关键的是在这个方法中该如何拿到原始对象呢?还是得需要一个Map,预习把这个Bean实例化后的对象存在这个Map中,那这样的话还不如直接用第一种方案,但是第一种又直接打破了Bean生命周期的设计。

所以,我们可以发现,现在Spring所用的singletonFactories,为了调和不同的情况,在singletonFactories中存的是lambda表达式,这样的话,只有在出现了循环依赖的情况,才会执行lambda表达式,才会进行AOP,也就说只有在出现了循环依赖的情况下才会打破Bean生命周期的设计,如果一个Bean没有出现循环依赖,那么它还是遵守了Bean的生命周期的设计的。由此看来,引入第三级缓存,是最优解!!