Spring是如何解决循环依赖的

何为循环依赖

理解:当对象A初始化进行赋值操作时,值的类型为引用类型B,需要从spring容器中获取具体的B对象,容器需要先创建B对象为A对象赋值,但是B对象进行赋值时,它内部存在属性类型为引用类型A变量,而A对象并没有初始化完毕,形成了循环依赖;

Spring在Bean哪个阶段可能出现循环依赖

理解:为Bean对象赋值阶段,值的类型为引用类型;

Spring如何解决循环依赖

需要明确几个要点:

  1. spring创建bean的实例化与初始化阶段是分开的;
  2. 实例化和初始化两个阶段的对象的属性值会改变但是对象引用地址不变的;

针对以上两点可以发现解决循环以来的方法:把完成了实例化但未完成初始化的对象提前暴露出去,让其他对象能够引用,这样就解决了循环依赖问题,spring中解决循环依赖方法是使用三级缓存;
首先介绍一下三级缓存:
一级缓存Map(singletonObjects)存放的是key为beanName值为已经完成初始化的Bean对象,
二级缓存Map(earlySingletonObjects)存放的是key为beanName值为完成了实例化但未完成初始化的Bean对象,
三级缓存Map(singletonFactories)key为beanName值为函数式接口对应的lambda表达式;

spring解决循环依赖的流程:

  1. 当A对象实例化后,会将A对象的beanName作为key,然后将获取已经完成实例化但未完成初始化A对象的Lambda函数作为值放入三级缓存;
  2. A对象进行初始化,为A对象的引用类型B属性赋值,在容器查找是否已经有B对象(这个查找其实就是在三级缓存中查找);
  3. 容器没有找到B对象,此时会去创建B对象;
  4. B对象完成实例化后,需要为B对象的引用类型A属性赋值,在容器中查找是否已经存在A对象,然后在三级缓存中找到A对象,调用三级缓存中lambda函数获取到了实例化但未初始化的A对象,赋值给自己的A属性,并将A对象放入二级缓存,移除三级缓存中A;
  5. 此时B对象实例化和初始化都执行完毕,完成B对象创建,将B对象放入一级缓存并移除B对象的二三级缓存,程序继续执行A对象初始化赋值,将B对象赋值给A对象的B属性,执行后续流程,A对象创建完毕;将A对象放入一级缓存并移除A对象的二三级缓存,

针对上述流程可以发现我们把源码改成把完成了实例化但未完成初始化的对象直接放入二级缓存,不需要三级缓存就能解决循环依赖;

二级缓存就能解决循环依赖,为什么需要三级缓存

  1. 上述所讲的情况是不存在代理对象的情况,当我们使用AOP需要创建代理对象二级缓存就无法解决该问题了;

首先需要明确与刚刚存在差异的点:

  1. 我们使用了AOP,需要创建代理对象,我们都知道,开启了AOP后,会在bean的赋值且InitMethod执行完成后,会执行BeanPostProcessor的postProcessAfterInitialization创建代理对象;

有了这一点我们只使用二级缓存的想法就会出现问题:

  1. A实例化后,为引用类型B属性赋值,这个时候创建B对象,并将二级缓存中A对象赋值给B对象;
  2. B赋值完成后,继续执行初始化流程,由于开启了AOP,最终需要执行BeanPostProcessor的postProcessAfterInitialization创建代理对象,这个时候就存在两个B对象,一个是前面实例化的B对象包含A属性,一个是后面创建的代理对象B不包含A属性;
  3. 最终会将代理对象B赋值给A的引用类型B属性,而代理对象B的引用类型A属性并未赋值,此时就存在了问题;

针对于上述问题Spring中是如何例用第三级缓存解决的:

  1. 当B给引用类型A赋值时,会去容器中查找A对象,发现第三级缓存中有A对象,调用三级缓存中lambda函数,由于开启了AOP,这里会创建A的代理对象覆盖原来的A对象,然后赋值给B对象的属性值A,当B对象执行BeanPostProcessor的postProcessAfterInitialization时会创建代理对象覆盖原来的B,此时B对象创建完成,添加到一级缓存,然后继续执行A的赋值流程,将B的代理对象赋值给A的属性B,完成A的对象创建;