Spring循环依赖


Spring循环依赖

  1. 解释下Spring的三级缓存
  2. 三级缓存分别是什么?三个Map有什么异同?
  3. 什么是循环依赖?一般说的Spring容器是什么?
  4. 如何检测是否存在循环依赖?实际场景中见过循环依赖的异常吗?
  5. 多例的情况下,循环依赖问题为什么无法解决?

什么是循环依赖

多个bean之间相互依赖,形成一个闭环。比如A依赖于B、B依赖于C、C依赖于A; 通常来说,指的是默认的单例Bean中,属性互相引用的场景

场景代码:

public class T1
{
	class A
	{
           B b;
	}
	
	class B
	{
           C c;
	}

	class C
	{
           A a;
	}
}

注意:如果AB循环依赖问题只要A的注入方式是setter并且singleton,就不会产生循环依赖问题

循环依赖报错问题

循环依赖现象在Spring容器中,注入的对象有2种情况 1.构造器注入(会报错)
构造器循环依赖无法解决,构造器注入支持循环依赖,是不存在的

2.以set方式注入(不会报错)

Spring容器内部是通过三级缓存来解决循环依赖问题(DefaultSingletonBeanRegistry)

只有单例的bean会通过三级缓存提前暴露解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中

  • 第一级缓存(也叫单例池)SingletonObjects:存放已经经历了完整生命周期的Bean对象
    Map形式<key,object>,ConcurrentHashMap
  • 第二级缓存:earlySingletonObjects,存放早期暴露出来的Bean对戏,Bean的生命周期未结束(属性还未填充完)
    Map形式<key,object>,HashMap
  • 第三级缓存:Map<String,ObjectFactory<?» singletonFactory,存放可以生成Bean的工厂
    Map形式<key,ObjectFactory>,HashMap

循环依赖源码

实例化/初始化

实例化

内存中申请一块内存空间,相当于租赁好房子,自己的家具还没有搬进去

初始化属性填充

完成属性的赋值,相当于家具带进场

三个Map和四个方法

  • 三个Map:SingletonObjects,SingletonObjects,singletonFactory
  • 四个方法:getSingleton(获取),doCreatBean(创建),populateBean(填充),addSingleton(添加)

第一级缓存SingletonObjects存放的是已经初始化好的Bean
第二级缓存SingletonObjects存放的是实例化了,但是还没没有初始化的Bean
第三级缓存singletonFactory存放的是FactoryBean,加入A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

AB两个对象在三级缓存中的迁移说明

  1. A创建过程中创建B,于是A将自己放到三级缓存里面,去实例化B;
  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再去查二级缓存,还是没有,再去查三级缓存,找到A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A;
  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B名然后完成创建,并将A自己放到一级缓存里面

总结

  1. 调用doGetBean()方法,想要获取beanA ,于是调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到beanA为null ,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中,然后回调匿名内部类的createBean方法
  5. AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断,是否为单例,是否允许提前暴露引用(对于单例 一般为true)、是否正在创建中(即是否是在第四步的集合中)判断为true则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB ,于是查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查询beanB ,没有则创建,然后给beanB填充属性
  8. 此时beanB依赖于beanA ,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找、此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取 到singletonObject ,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
  9. 这样beanB就获取到了 beanA的依赖,于是beanB顺利完成初始化,并将beanA从三级缓存移动到二级缓存中
  10. 随后beanA继续他的属性填充工作,此时也获取到了 beanB , beanA也随之完成了创建,回到getSingleton()方法中继续向下执行,将beanA从二级缓存移动到 一级缓存中

Spring解决循环依赖

Spring解决循环依赖核心在于提前曝光

  1. A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行第二步填充属性
  2. 发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A)
  3. 尝试从一级缓存singletonObjects中获取(肯定没有,因为A还没初始化完全)
  4. 尝试从二级缓存earlySingletonObjects中获取(也没有)
  5. 尝试三级缓存,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过从三级缓存中拿到A的工厂对象,通过这个工厂对象构建出了A的Bean对象,B拿到A对象后顺利完成了初始化阶段1、2、3。B对象完全初始化之后将自己放入到一级缓存singletonObjects中
  6. 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:
第一步,先获取到三级缓存中的工厂;
第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

为什么需要三级缓存,不能使用二级缓存吗?

**注意:三级缓存实际上只与Spring的AOP有关**

如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。若使用二级缓存,在AOP情形下,注入到其他的bean,不是最终的代理对象,而是原始对象。

Spring的设计原则是bean的实例化、属性设置、初始化之后生成aop对象,但是为了解决循环依赖但是又不打破这个设计原则,所以使用了第三级缓存;如果使用二级缓存的话,可以将aop代理工作提前到暴露实例化的阶段执行,也就是说所有的bean在创建过程中先生成代理对象再初始化和其他工作,但是这样就和Spring的aop的设计原则相悖,aop的实现需要与bean的正常生命周期的创建分离;这样只有使用第三级缓存封装一个函数式接口对象到缓存中, 发生循环依赖时,触发代理类的生成

衍生问题:三级缓存提高了效率吗?

并没有,二级缓存与三级缓存唯一区别则是A创建代理的时机不同




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 2379. Minimum Recolors to Get K Consecutive Black Blocks
  • 2471. Minimum Number of Operations to Sort a Binary Tree by Level
  • 1387. Sort Integers by The Power Value
  • 2090. K Radius Subarray Averages
  • 2545. Sort the Students by Their Kth Score