引言
在使用 Spring 框架进行开发时,循环依赖是一个常见且容易让人困惑的问题。当多个 Bean 之间相互依赖形成闭环时,就会引发循环依赖,可能导致应用启动失败。本文将详细探讨 Spring 循环依赖的产生场景、Spring 处理循环依赖的机制以及循环依赖无法解决的情况。
一、Spring 循环依赖的产生场景
1. 构造器注入循环依赖
构造器注入循环依赖是指在 Bean 的构造函数中相互依赖,从而形成死循环。以下是示例代码:
public class A {
private final B b;
public A(B b) {
this.b = b;
}
}
public class B {
private final A a;
public B(A a) {
this.a = a;
}
}
在这个例子中,A
的构造函数需要 B
的实例,而 B
的构造函数又需要 A
的实例。Spring 在创建 A
时,为了完成实例化,会先尝试创建 B
;而创建 B
时又需要先创建 A
,这样就陷入了无限循环。
2. setter 注入循环依赖
setter 注入循环依赖是指 Bean 通过 setter
方法相互依赖。示例代码如下:
@Component
public class A {
@Autowired
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
@Autowired
private A a;
public void setA(A a) {
this.a = a;
}
}
A
通过 setter
方法依赖 B
,B
同样通过 setter
方法依赖 A
,形成了循环依赖关系。
二、Spring 处理循环依赖的机制
Spring 主要利用三级缓存来解决单例 Bean 的 setter
注入循环依赖问题,下面详细介绍这三级缓存:
- 一级缓存(
singletonObjects
) :也称为单例对象缓存,用于存放已经完全初始化好的单例 Bean 实例。这些 Bean 已经完成了实例化、属性注入和初始化等所有流程,可以直接使用。
- 二级缓存(
earlySingletonObjects
) :即早期单例对象缓存,存储的是已经实例化但还未完成属性注入和初始化的单例 Bean 实例。这些 Bean 处于中间状态,还不能完全正常使用。
- 三级缓存(
singletonFactories
) :单例工厂缓存,存放的是生产单例 Bean 的工厂对象。通过这些工厂对象,可以获取到早期的单例 Bean 实例。
以
A
和 B
相互依赖为例,Spring 解决循环依赖的具体流程如下:
-
创建
A
实例:Spring 开始创建A
,首先进行实例化操作,然后将A
的工厂对象放入三级缓存singletonFactories
中。此时A
仅仅完成了实例化,还未进行属性注入和初始化。 -
注入
A
的依赖B
:在对A
进行属性注入时,发现需要B
实例,于是 Spring 开始创建B
。 -
创建
B
实例:实例化B
后,将B
的工厂对象放入三级缓存singletonFactories
中,接着对B
进行属性注入。 -
注入
B
的依赖A
:在对B
进行属性注入时,需要A
实例。这时 Spring 从三级缓存singletonFactories
中获取A
的工厂对象,通过该工厂对象获取A
的早期实例,将其放入二级缓存earlySingletonObjects
中,并从三级缓存移除。然后将A
的早期实例注入到B
中。 -
完成
B
的初始化:B
完成属性注入和初始化后,将B
实例放入一级缓存singletonObjects
中,并从二、三级缓存中移除。 -
完成
A
的初始化:将B
实例注入到A
中,A
完成属性注入和初始化后,将A
实例放入一级缓存singletonObjects
中,并从二、三级缓存中移除。
三、循环依赖无法解决的情况
1. 构造器注入循环依赖
由于构造器注入是在实例化对象时就需要依赖对象,Spring 无法在实例化阶段打破这种循环依赖。在创建对象时,必须先满足构造函数的参数要求,而循环依赖导致参数永远无法满足,因此构造器注入循环依赖无法解决。
2. 非单例 Bean 的循环依赖
Spring 只对单例 Bean 的循环依赖进行处理。对于原型(prototype
)作用域的 Bean,每次请求都会创建新的实例,Spring 不会对这些 Bean 进行缓存。这就导致在处理循环依赖时,无法像单例 Bean 那样通过缓存获取早期实例,从而无法解决循环依赖问题
四、总结
Spring 循环依赖是开发中常见的问题,理解其产生的原因和 Spring 的处理机制有助于我们更好地编写代码。Spring 通过三级缓存机制巧妙地解决了单例 Bean 的 setter
注入循环依赖问题,但对于构造器注入和非单例 Bean 的循环依赖,目前还无法有效处理。在实际开发中,我们应尽量避免出现循环依赖