Spring如何处理循环依赖?这是最近较为频繁被问到的一个面试题,在前面Bean实例化流程中,对属性注入一文多多少少对循环依赖有过介绍,这篇文章详细讲一下Spring中的循环依赖的处理方案。
什么是循环依赖
依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者多个Bean相互依赖
构造器循环依赖
代码示例:
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB){
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA){
this.beanA = beanA;
}
}
复制代码
配置文件
<bean id="beanA" class="cn.itsource._01_di.BeanA" >
<constructor-arg type="cn.itsource._01_di.BeanB" ref="beanB" />
</bean>
<bean id="beanB" class="cn.itsource._01_di.BeanB" >
<constructor-arg type="cn.itsource._01_di.BeanA" ref="beanA" />
</bean>
复制代码
Setter循环依赖
代码示例
public class BeanA {
private BeanB beanB;
public void setBeanB(BeanB beanB){
this.beanB = beanB;
}
}
@Data
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA){
this.beanA = beanA;
}
}
复制代码
配置文件
<bean id="beanA" class="cn.itsource._01_di.BeanA" >
<property name="beanB" ref="beanB" />
</bean>
<bean id="beanB" class="cn.itsource._01_di.BeanB">
<property name="beanA" ref="beanA" />
</bean>
复制代码
循环依赖包括: 构造器注入循环依赖 set , 注入循环依赖 和 prototype模式Bean的循环依赖。Spring只解决了单例Bean的 setter 注入循环依赖,对于构造器循环依赖,和 prototype模式的循环依赖是无法解决的,在创建Bean的时候就会抛出异常 :“BeanCurrentlyInCreationException” ,
循环依赖控制开关在 AbstractRefreshableApplicationContext 容器工厂类中有定义:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Nullable
private Boolean allowBeanDefinitionOverriding;
//是否允许循环依赖
@Nullable
private Boolean allowCircularReferences;
//设置循环依赖
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
复制代码
默认情况下是允许Bean之间的循环依赖的,在依赖注入时Spring会尝试处理循环依赖。如果将该属性配置为“false”则关闭循环依赖,当在Bean依赖注入的时遇到循环依赖时抛出异常。可以通过如下方式关闭,但是一般都不这么做
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//禁用循环依赖
applicationContext.setAllowCircularReferences(false);
//刷新容器
applicationContext.refresh();
...
复制代码
构造器循环依赖处理
构造器是不允许循环依赖的,动动你的小脑瓜想一想,比如:A 依赖 B ,B依赖C,C依赖A,在实例化A的时候,构造器需要注入B,然后Spirng会实例化B,此时的A属于“正在创建”的状态。当实例化B的时候,发现构造器需要注入C,然后去实例化C,然而实例化C的时候又需要注入A的实例,这样就造成了一个死循环,永远无法先实例化出某一个Bean,所以Spring遇到这里构造器循环依赖会直接抛出异常。
那么Spring到底是如何做的呢?
- 首先Spring会走Bean的实例化流程尝试创建 A 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找A 是否正在创建,如果没找到,则将 A 放入 “正在创建Bean池”中,然后准备实例化构造器参数 B。
- Spring会走Bean的实例化流程尝试创建 B 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找B 是否正在创建,如果没找到,则将 B 放入 “正在创建Bean池”中,然后准备实例化构造器参数 A。
- Spring会走Bean的实例化流程尝试创建 A 的实例 ,在创建实例之间先从 “正在创建Bean池” (一个缓存Map而已)中去查找A 是否正在创建。
- 此时:Spring发现 A 正处于“正在创建Bean池”,表示出现构造器循环依赖,抛出异常:“B