浅谈Spring中的循环依赖

什么是循环依赖?

        循环依赖就是循环引用,就是两个或者多个bean相互之间的持有对方,比如TestA引用TestB,TestB引用TestC,TestC引用TestA,这样它们最终反映为一个环。这里需要强调一点,此情形不是循环调用,循环调用是方法之间的环调用循环引用如下图:

浅谈Spring中的循环依赖_第1张图片

 只有单例的Bean才存在循环依赖的情况原型(Prototype)情况下,Spring会直接抛出异常。原因很简单,AB循环依赖,A实例化的时候,发现依赖B,创建B实例,创建B的时候发现需要A,创建A1实例……无限套娃,直接把系统干垮。

Spring可以解决哪些情况的循环依赖?

浅谈Spring中的循环依赖_第2张图片

第四种可以而第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。

所以简单总结,当循环依赖的实例都采用setter方法注入的时候,Spring可以支持,都采用构造器注入的时候,不支持,构造器注入和setter注入同时存在的时候,emm......看命

那么Spring是怎么解决循环依赖的呢?

我们都知道,单例Bean初始化完成,要经历三步

实例化========>属性赋值========>初始化

注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:

  1. 一级缓存 : Map singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例

  2. 二级缓存 : Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例

  3. 三级缓存 : MapsingletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。

我们来看一下三级缓存解决循环依赖的过程:

当 A、B 两个类发生循环依赖时:

浅谈Spring中的循环依赖_第3张图片

1. 创建A实例,实例化的时候把A对象⼯⼚放⼊三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道

2.A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B

3.同样,B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存。

浅谈Spring中的循环依赖_第4张图片

4.接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存

5.最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象

浅谈Spring中的循环依赖_第5张图片

 所以,我们就知道为什么Spring能解决setter注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。

参考文献:面渣逆袭:Spring三十五问,四万字+五十图详解!建议收藏!

你可能感兴趣的:(spring,java,前端)