一、什么是循环依赖
循环依赖即循环引用,形成闭环。比如,A 依赖 B,B 又依赖 A,形成了循环依赖;或者 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖;更或者是自己依赖自己。如图:
这不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。Spring 循环依赖场景有:
- 构造器的循环依赖
- 构造器依赖 + field/setter 依赖(A 的构造器依赖 B,B 的 field/setter 依赖 A)
- field 依赖或者 setter 依赖
具体情况如下:
1️⃣A 的构造方法中依赖 B 的实例对象,同时 B 的构造方法中依赖 A 的实例对象。(无法解决)
2️⃣A 的构造方法中依赖 B 的实例对象,同时 B 的某个 field/setter 需要 A 的实例对象,以及反之。(可以解决)
3️⃣A 的某个 field/setter 依赖 B 的实例对象,同时 B 的某个 field/setter 依赖 A 的实例对象,以及反之。(可以解决)
二、Spring 三大循环依赖
1️⃣构造器注入循环依赖【不能解决】
表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException表示循环依赖。
@Service
public class A {
public A(B b) {
}
}
@Service
public class B {
public B(A a) {
}
}
Spring 容器会将每一个正在创建的 Bean 标识符放在一个“当前创建 Bean 池”中,Bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 Bean 过程中发现自己已经在“当前创建 Bean 池”里时将抛出BeanCurrentlyInCreationException表示循环依赖;而对于创建完毕的 Bean 将从“当前创建 Bean 池”中清除掉。执行结果报错信息为:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation:
Is there an unresolvable circular reference?
根本原因:Spring 解决循环依赖依靠的是 Bean 的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器表示已经完成实例化,所以构造器的循环依赖无法解决。
2️⃣Singleton Pattern field 属性注入循环依赖【可以解决】
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
结果:项目启动成功,能够正常工作。
备注:setter 方法注入方式原理同字段注入方式类似。
3️⃣prototype 模式 field 属性注入循环依赖【不能解决】
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
@Autowired
private B b;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
@Autowired
private A a;
}
对于prototype 作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不缓存 prototype 作用域的 bean,因此无法提前暴露一个创建中的 bean。
scope="prototype"
意思是每次请求都会创建一个实例对象。两者的区别是:有状态的 bean 都使用 Prototype 作用域,无状态的一般都使用 singleton 单例作用域。
因此本例中启动时是不会报错的(因为非单例 Bean 默认不会初始化,而是使用时才会初始化),所以需要手动 getBean() 或者在一个单例 Bean 内 @Autowired 一下它即可:
// 在单例Bean内注入
@Autowired
private A a;
打印结果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
三、怎么检测是否存在循环依赖
检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 打标,如果递归调用回来发现正在创建中的话,说明循环依赖了。
四、Spring 是如果解决循环依赖的
Spring 通过三级缓存加上“提前曝光”机制,配合 Java 的对象引用原理,比较完美地解决了某些情况下的循环依赖问题。
- A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中。此时进行初始化的第二步,发现自己依赖对象 B,就尝试去 get(B),发现 B 还没有被 create,所以走 create 流程。
- B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get(A),由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象。
- B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化。