什么是循环依赖

Spring IoC的循环依赖就是循环引用,意思就是超过两个以上的bean互相依赖对方,最终形成闭环。比如A的实例化需要依赖B对象,B的实例化需要依赖C对象,C的实例化需要依赖A对象,这就会导致,A的实例化时需要等待B的实例化,B的实例化时需要等待C的实例化,C的实例化时需要等待A的实例化,这样就导致,A的实例化操作永远无法进行。
image.png

循环依赖的处理

两种场景下的循环依赖无法处理

single bean下的构造器的循环依赖

构造器要调用构造函数new一个对象出来,而参数又依赖另一个对象。创建类A依赖于类B,创建参数中的类B发现类B不存在就会出错抛出BeanCurrentlyInCreationException异常。
使用构造器需要完成new实例化过程,因为需要携带依赖Bean的参数,依赖的Bean没有实例化,无法放入到三级缓存当中。

prototype原型bean循环依赖

原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过set方法产生的循环依赖都会抛出异常。
AbstractBeanFactory.doGetBean()方法
image.png
image.png

image.png

如何解决singleton bean的循环依赖

针对singleton bean通过setXxx或者@Autowired进行的循环依赖,可以通过三级缓存的方案去解决。

bean的生命周期流程

(1)实例化:执行bean的构造方法,bean中依赖的对象还未赋值;
(2)设置属性:给bean(对象A)中依赖的对象(对象B)赋值,若被依赖的对象(对象B)尚未初始化,则先进行被依赖对象(对象B)的生命周期;
(3)初始化:执行bean的初始化方法等操作;

解决循环依赖的原理

所以,Spring的循环依赖的依据基于Java的引用传递,构造器必须在获取对象引用之前实例化,但是当获得依赖对象的引用时,对象的属性是可以延后设置的(比如setXxx方法)。
Spring通过setXxx或者@Autowired解决循环依赖是通过提前暴露一个ObjectFactory对象来完成,意思就是ClassA调用构造器完成对象初始化后,在调用ClassA的setClassB方法之前就会把ClassA实例化的对象通过ObjectFactory对象提前交给Spring容器。

解决循环依赖的思路

在实例化与设置属性之间引入缓存机制,将已经创建好实例但是还没有设置属性的bean存放到缓存里(缓存中存储的是没有属性设置的实例对象)。
假设对象A和对象B相互依赖,对象A的创建需要引用到对象B,而对象B的创建需要引用对象A。在创建对象A的时候就可以将其放入到缓存中,当创建对象B的时候就直接从缓存里引用对象A(对象A尚未做属性设置,所以为半成品对象A)。当对象B利用半成品对象A完成实例创建后(实例化->设置属性->初始化),再被对象A引用过去,完成对象A的创建(属性设置->初始化)。

缓存机制

image.png

源码 级别 描述
singletonObjects 一级缓存 存放完全初始化好的bean,完成了实例化、属性设置、初始化三个步骤的bean,拿出来的bean可以直接使用。(单例池)
earlySingletonObjects 二级缓存 存放原始的bean对象,此时的对象只进行了实例化但是没有填充属性,也就是存放半成品对象。
二级缓存主要就是用来解决循环依赖的问题。
singletonFatories 三级缓存 用来存放bean工厂对象,工厂对象是用来生产bean对象的实例。

解决循环依赖的流程

(1)先从一级缓存里取bean实例,若没有找到对应的bean实例,再从二级缓存里面取半成品对象的bean实例。
(2)若从二级缓存里获取不到bean实例,则从三级缓存singletonFatories里获取,由于三级缓存存放着可以产生bean实例的工厂类,因此可以通过该工厂类产生bean实例。
(3)调用工厂类的getObject方法返回半成品bean对象的引用,也可以称为earlySingletonObject。将半成品bean放到二级缓存里,在三级缓存里删除该bean。
(4)等到半成品bean设置了属性之后,再将完整初始化后可以使用的bean移动到以及缓存里。
(5)完成以上的步骤,处理循环依赖的过程也就结束。
image.png
image.png
image.png
image.png

处理循环依赖的举例

实例化对象A -> 创建A实例的工厂类存放到三级缓存 -> 对象A属性填充时,发现依赖B ->
实例化对象B -> 创建B实例的工厂类存放到三级缓存 -> 对象B属性填充时,发现依赖A ->
循环依赖问题出现 ->
从一,二级缓存获取不到实例A,通过三级缓存的工厂类创建实例A ->
并将创建后的实例A放入二级缓存,移除在三级缓存中的工厂类(getSingleton)->
实例A是半成品,没有完成属性填充 ->
通过三级缓存的工厂类创建实例B,从二级缓存中获取半成品实例A进行属性填充,放入到二级缓存 ->
最后进行B的初始化,完成B的完整创建,实例B移动到一级缓存(addSingleton)->
对象A进行属性填充(populateBean),发现依赖的Bean B在一级缓存中存在 ->
对象A完成属性填充,移动到一级缓存中。

处理循环依赖举例.png