还需要通过梳理源码,形成流程图

  1. // A依赖了B
  2. class A{
  3. public B b;
  4. }
  5. // B依赖了A
  6. class B{
  7. public A a;
  8. }
  9. A a = new A();
  10. B b = new B();
  11. a.b = b;
  12. b.a = a;

在普通情况下,循环依赖不是什么问题。但在Spring中需要处理这个问题。因为Spring中的实例生成过程,不是简单的new出来的。Spring中实例化,会经历很多步骤。
循环依赖场景

  1. 注入的循环依赖,Spring已经默认解决。
  2. 自己注入自己,也是循环依赖,Spring已经默认解决。
  3. DependOn导致的循环依赖,Spring没有解决。
  4. 原型情况下两个bean循环依赖,Spring无法解决。如果其中一个是单例,则可以解决。(因为两个都是原型无法进行缓存)
  5. 使用构造函数实例化过程时,存在循环依赖,默认情况下Spring无法解决(因为无法生成实例)。可以手动使用Lazy注解解决

使用Lazy注解特性,解决循环依赖问题。

使用Lazy标记后,注释过程中,会生成一个代理对象,赋值给属性。只有在真正使用属性时,代理对象才会调用真正的注入逻辑。从而规避了循环依赖的情况

一、三级缓存

  • (一级)singletonObjects:缓存已经经历完整生命周期的bean对象。
  • (二级)earlySingletonObjects:缓存的是早期的bean对象(未经过完整生命周期的bean,三级缓存中Lambda表达式执行的结果)。如果某个bean存在循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中。如果要经过AOP,则会把代理对象放入earlySingletonObjects中,否则把原始对象放入earlySingletonObjects,即使是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,也会放入earlySingletonObjects。可以统一认为是未经过完整生命周期的bean。
  • (三级)singletonFactories:(实例化后、依赖注入前放入,用于打破循环的关键)缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂。缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,则Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖(当前正在创建的bean被其他bean依赖),则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。(保证存在循环依赖情况下,AOP按照正常的生命周期流程进行
  • earlyProxyReferences,用来记录某个原始对象是否进行过AOP。避免在初始化化后,重复进行AOP动作
  • singletonsCurrentlyInCreation:记录正在创建的bean

    二、解决流程

    1.3.循环依赖 - 图1
    问题:基于上面过程中可以得出,新增二级缓存就能解决循环依赖,那么为什么Spring中还引入三级缓存singletonFactories呢?
    基于上面的场景引申:如果A的原始对象注入给B的属性之后,A在原始对象基础上进行了AOP,产生了一个代理对象,此时对于A而言,它的Bean对象应该是AOP之后的代理对象,而B的a属性仍然是普通对象,这就产生不一致。最终导致B依赖的A和最终的A不是同一个对象的现象。singletonFactories是存放的最终的代理对象的。
    AOP是Spring中除IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功能想要并存,Spring需要借助singletonFactories进行特殊处理。
    总结一句:如果循环依赖没有AOP操作。二级缓存是可以 解决的。但是Spring中为了解决循环依赖、同时解决AOP,所以引入三级就缓存。

问题2:如果将AOP操作提前至实例化后、属性注入之前进行,通过二个缓存也可以解决。
如果调整AOP顺序,则会因为解决循环依赖问题,破坏Spring的生命周期顺序。所以引进第三级缓存,为了解决循环依赖,只是对循环依赖的情况,提前进行AOP操作。
为了统一实现,在三级缓存中存放lamda表达式,如果需要AOP,则执行lamda表达式获得代理对象,否则lamda表达式返回原始对象。lamda返回的(代理或原始)对象,放入二级缓存中(避免重复执行lamda表达式)。
注意:
避免重复AOP。提前AOP后,会利用map记录已经完成AOP。初始化后步骤后,不会再进行AOP操作。
判断存在循环依赖。同时Spring内部使用Set集合记录正在创建的Bean,如果正在创建的bean再次发起创建,则说明存在循环依赖

三、循环依赖场景

场景一、AService需要AOP操作,且存在循环依赖时,则AService需要提前进行AOP动作。

引入三级缓存,存放AOP的lambda表达式。只有出现循环依赖时,才执行lambda表达式。
创建AService,执行AService的生命周期。

  1. 记录AService处于实例化中状态。singletonsCurrentlyInCreation。
  2. 实例化AService(得到实例对象)。向三级缓存进行缓存AService(执行AOP的Lambda表达式,需要,则后续执行;不需要,后续不执行)。
  3. 填充AService中属性(BService)—>在一级单例池中查找BService实例,没有—>通过singletonsCurrentlyInCreation判断BService是否创建中,没有—>则二级缓存中查找,没有,则创建BService
    1. 实例化BService(得到实例对象)
    2. 填充BService的属性(AService)—>在单例池中查找AService实例,没有—>通过singletonsCurrentlyInCreation可知A处于创建状态(出现循环依赖)—>二级缓存中查找,没有—>获取三级缓存中Lambda表达式执行(如果AService不需要AOP,则返回原始对象;需要AOP,进行AOP得到代理对象)—>放入二级缓存中[如果不提前AOP,则BService中属性会被赋值原始的AService]。—>将原始对象或者代理对象放入二级缓存中(清理三级缓存中对应缓存)
    3. 完成属性AService和其他属性填充。
    4. 继续生命周期过程(初始化前、初始化、初始化后)
    5. 完成生命周期;BService实例放入单例池。
  4. 填充其他属性
  5. 继续生命周期过程(初始化前、初始化)
  6. 初始化后,完成代理对象生产
  7. 完成生命周期;AService实例放入单例池。

    场景二、利用二级缓存,避免CService重新创建AService

    二级缓存:如果正在创建的对象不需要AOP,则存放原始实例。如果需要AOP,则存储完成AOP的实例
    创建AService,执行AService的生命周期。

  8. 记录AService处于实例化中状态。singletonsCurrentlyInCreation。

  9. 实例化AService(得到实例对象)。向三级缓存进行缓存AService(执行AOP的Lambda表达式,需要,则后续执行;不需要,后续不执行)。
  10. 填充AService中属性(BService)—>在一级单例池中查找BService实例,没有—>通过singletonsCurrentlyInCreation判断BService是否创建中,没有—>则二级缓存中查找,没有,则创建BService
    1. 实例化BService(得到实例对象)
    2. 填充BService的属性(AService)—>在单例池中查找AService实例,没有—>通过singletonsCurrentlyInCreation可知A处于创建状态(出现循环依赖)—>二级缓存中查找,没有—>获取三级缓存中Lambda表达式执行(如果AService不需要AOP,则返回原始对象;需要AOP,进行AOP得到代理对象)—>放入二级缓存中[如果不提前AOP,则BService中属性会被赋值原始的AService]。—>将原始对象或者代理对象放入二级缓存中(清理三级缓存中对应缓存)
    3. 完成属性AService和其他属性填充。
    4. 继续生命周期过程(初始化前、初始化、初始化后)
    5. 完成生命周期;BService实例放入单例池。
  11. 填充其他属性
    1. 实例化CService(得到实例对象)
    2. 填充CService属性(AService)—>在单例池中查找AService实例—>通过singletonsCurrentlyInCreation可知A处于创建状态—>出现循环依赖—>从二级缓存中获取,AService提前进行AOP得到代理对象(避免重新创建AService代理对象)。
    3. 完成AService和其他属性填充。
    4. 继续生命周期过程(初始化前、初始化、初始化后)
    5. 完成生命周期;BService实例放入单例池。
  12. 继续生命周期过程(初始化前、初始化)
  13. 初始化后,完成代理对象生产
  14. 完成生命周期;AService实例放入单例池。

二级缓存,存放进行中的实例(三级缓存中lamda表达式的执行结果),保证实例化中的实例是单例的
三级缓存,是打破循环依赖的关键。如果没有循环依赖,则不会用到三级缓存中的数据。在出现了循环依赖的情况下才会打破Bean生命周期的设计,即:提前进行AOP操作。

如果循环依赖的实例。作用域是原型的。则无法解决循环依赖,无法使用缓存

四、源码分析

image.png
image.png

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. //根据beanName从一级单例池(存放完整的bean实例)获取对应bean实例
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  5. //使用全局变量进行锁定,避免同一实例多次创建,
  6. synchronized (this.singletonObjects) {
  7. //根据beanName从一级单例池(存放正在创建的半成品bean实例)获取对应bean实例。
  8. singletonObject = this.earlySingletonObjects.get(beanName);
  9. if (singletonObject == null && allowEarlyReference) {
  10. //从三级缓存中获取Lamda表达式,并且执行。结果为:1.实例化的Bean或者2.完成AOP的代理对象(如果执行AOP逻辑。需要向缓存earlyProxyReference中设置标识。避免初始化后逻辑中再次执行AOP)
  11. //只有存在循环依赖时,才会向三级缓存中,添加lamda逻辑。
  12. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  13. if (singletonFactory != null) {
  14. singletonObject = singletonFactory.getObject();
  15. //因为lamda表达式,执行结果,未完成生命周期的半成品。故将执行结果放入二级缓存。
  16. this.earlySingletonObjects.put(beanName, singletonObject);
  17. this.singletonFactories.remove(beanName);//三级缓存不会再次执行。故可以移除。同时避免再次执行
  18. }
  19. }
  20. }
  21. }
  22. return singletonObject;
  23. }