文章目录
一、什么是循环依赖?
循环依赖指的是2个或以上的bean互相持有对方,最终形成闭环。注意区分循环调用(死循环,除非有终止条件)。
二、循环依赖的场景
1.构造器的循环依赖
代码如下(示例):
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private C c;
public B(C c) {
this.c = c;
}
}
@Component
public class C {
private A a;
public C(A a) {
this.a = a;
}
}
2.setter循环依赖
代码如下(示例):
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private C c;
public void setC(C c) {
this.c = c;
}
}
@Component
public class C {
private A a;
public void setA(A a) {
this.a = a;
}
}
二、spring如何检测有循环依赖
spring在创建对象的时候,会为当前正在创建的bean的标识符放到bean池中,待该bean创建成功后会从池中移除该bean标识符,基于这个原理,当创建A bean的时候,(会到池中查询是否本身的标识符已存在,不存在)会到会将A的标识符放到池中,A发现依赖B,则创建B bean,当创建B bean的时候(会到池中查询是否本身的标识符已存在,不存在)将B bean的标识符放到池中,B发现要依赖C,则创建C bean,当创建C bean的时候(会到池中查询是否本身的标识符已存在,不存在)将C bean的标识符放到池中,C发现需依赖A,则创建A,这时候发现A的创建标识符在池中(表示A正在创建中),则发现有循环依赖。
二、spring如何解决循环依赖
1.构造器的循环依赖 —无法解决
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
springboot项目中检测到有构造器循环依赖,项目都不让起起来。
2.setter循环依赖(适用场景只有单例)
我们知道,当Spring创建单例对象主要分为三步,
1:调用构造器实例化
2:填充属性
3:初始化
因此循环依赖主要发生在第一、第二步,也就是构造器循环依赖和field循环依赖。所以,Spring先用构造器实例化Bean对象,将实例化结束的对象(此时该bean还未进行初始化的第二和第三步,但至少能用了)放到一个缓存中(提前暴露一个单例对象工厂),该工厂提供获取这个未设置属性的实例化对象的引用方法。根据上述例子,当Spring实例化了A、B、C后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,解决循环的问题。以上也能解释为何构造器依赖无法解决,因为构造器依赖无法提供一个至少可用的bean。
此外Spring创建对象时涉及到以下三级缓存:
singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache
这里先记录一下,回头有空再深入研究下。
总结
尽快Spring能解决Setter注入循环依赖,但是我们在编码过程中尽量避免这种情况,可以在代码review时使用工具检测写的代码是否有巡逻依赖,尽量保持项目代码清爽整洁,写代码设计过程中保持一点洁癖的工匠精神。