目录
什么是循环依赖?
在 Spring 中,循环依赖指的是当两个或多个 Bean 彼此之间存在直接或间接的依赖关系,形成了一个闭环,导致 Spring 容器无法完成 Bean 的实例化和装配的情况。
循环依赖通常发生在以下情况下:
-
类之间的相互引用:如果两个类相互引用对方,例如 ClassA 中包含一个 ClassB 的成员变量,而 ClassB 中也包含一个 ClassA 的成员变量,就会形成循环依赖。
-
构造函数循环依赖:当两个或多个 Bean 的构造函数参数中相互依赖对方的 Bean 实例时,就会出现构造函数循环依赖。
Spring 框架通过使用三级缓存解决循环依赖问题。当创建 Bean 的过程中发现循环依赖时,Spring 会将正在创建的 Bean 实例提前暴露出来,暂时使用一个半初始化状态的 Bean 代替,待循环依赖的 Bean 创建完毕后再完成初始化和依赖注入。
尽管 Spring 提供了解决循环依赖的机制,但循环依赖通常被认为是一种设计上的缺陷,应尽量避免。因为循环依赖会增加代码的复杂性,降低代码的可读性和可维护性,可能导致程序运行时的死锁或其他不可预测的行为。因此,在设计应用程序时,应尽量避免出现循环依赖。
解决循环依赖的方法
Spring中通过三级缓存的方式可以解决大部分循环依赖问题
spring中的三级缓存指的是:
缓存名称 | 源码名称 | 作用 |
一级缓存 | singleonObjects | 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象 |
二级缓存 | earlySingleonObjects | 缓存生命周期还没有走完的bean对象 |
三级缓存 | singletonFactories | 缓存的是ObjectFactory,表示对象工厂,可以用来创建代理对象或普通对象 |
一级缓存作用:限制bean在beanFactory中只存在一份,及实现singleton scope,解决不了循环依赖问题
加入一级缓存的执行流程:
可以看到,一级缓存的加入并没有对解决循环问题起到什么作用,我们如果想要打破这个循环,就需要一个bean的中间对象,这个中间对象就是二级缓存。
加入二级缓存之后的执行流程:
可以看到,加入二级缓存之后,A对象在进行初始化的时候要去容器中注入B对象,此时容器中还不存在B对象,于是就将还没有初始化完的半成品A对象加入二级缓存中,转而去实例化B对象,在B对象初始化注入A对象的时候,在二级缓存中就可以得到A对象,于是循环就在这里被打破了,这里B对象就可以创建成功,将B对象存入一级缓存中,同时将B对象注入给A,A对象也创建成功,将A对象也出入一级缓存中,这样,对于普通对象产生的循环依赖问题我们就已经解决了。
不过二级缓存不能解决代理对象的循环依赖问题,那么我们就需要三级缓存。
加入三级缓存的执行流程如下:
ObjectFactory既可以创建普通对象,也可以创建代理对象。
那么是不是我们就不需要二级缓存了呢?
不是的,因为每次ObjectFactory每次都会创建一个新的对象,在从三级缓存中获取了一个对象的ObjectFactory创建一个对象实例之后,就将这个ObjectFactory移出三级缓存,将对象实例存入二级缓存之中,之后还是从二级缓存中得到这个bean对象,这样是保证了bean对象在生命周期中的单例性。
不过三级缓存解决不了构造方法出现循环依赖。
原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的依赖注入
解决方法:使用@Lazy进行懒加载,什么时候需要使用对象再进行bean对象的创建。