什么是循环依赖
循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 TestA 引用 TestB,TestB 引用 TestC,TestC引用 TestA,则他们最终会形成一个闭环。
循环调用是无法解决的,除非有终结的条件,否则就是死循环,最终导致内存溢出错误。
Spring 是如何解决循环依赖的
循环依赖定义
public class TestA {
private TestB testB;
public void a(){
testB.b();
}
public TestB getTestB() {
return testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
public class TestB {
private TestC testC;
public void b(){
testC.c();
}
public TestC getTestC() {
return testC;
}
public void setTestC(TestC testC) {
this.testC = testC;
}
}
public class TestC {
private TestA testA;
public void c(){
testA.a();
}
public TestA getTestA() {
return testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}
构造器循环依赖
-
表示通过构造器注人构成的循环依赖,此依赖是无法解决的
,只能拋出 BeanCurcenlylCreationException 异常表示循环依赖 -
如在创建 TestA类时,构造器需要 TestB 类,那将去创建 TestB,在创建 TestB 类时又发现需要 TestC 类,则又去创建TestC,最终在创建 TestC 时发现又需要 TestA,从而形成一个环,没办法创建。
-
Spring 容器将每一个正在创建的 bean 标识符放在一个
当前创建 bean 池
中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建 bean 过程中发现自己已经在当的创建bean 池
里时,将抛出BeanCurrentlyInCreationException
异常表示循环依赖;而对于创是完毕的bean 将从当前创建bean池中清除掉。
我们通过一个直观的测试用例来进行分析。1.创建配置文件。
‹bean id="testA" class="com.bean.TestA"> <constructor-arg index="0" ref="testB"/> </bean> ‹bean id="testB" class="com.bean.TestB"> <constructor-arg index="0" ref="testC"/> </bean> ‹bean id="testC" class="com.bean.TestC"> <constructor-arg index="0" ref="testA"/> </bean>
-
创建测试用例。
@Test (expected = BeanCurrentlyInCreationException.class) public void testCircleByConstructor () throws Throwable{ try { new ClassPathXmlApplicationContext ("test.xml") ; } catch (Exception e) { //因为要在创建testC时抛出; Throwable el = e.getCause().getCause ().getCause(); throw el; } }
-
针对以上代码的分析如下
- Spring 容器创建
testA
bean,首先去当前创建 bean池
查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数testB
,并将testA
标识料放到当前创建bean池
。 - Spring 容器创建
testB
bean,首先去当前创建 bean池
查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数testC
,并将testB
标识符放到当前创建bean池
。 - Spring 容器创建
testC
bean,首先去当前创建 bean池
查找是否当前 bean正在创建,如果没发现,则继续准备其需要的构造器参数testA
,并将testc
标识符放到当前创建 bean 池
- 到此为止 spring 容器要去创建
testA
bean,发现该 bean 标识符存在当前创建 bean 池中,因为表示循环依赖,抛出BeanCurrentlyInCreationException
setter 循环依赖
表示通过 setter 注入方式构成的循环依赖
。对于 setter 注人造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤
(如 setter 注入)的bean来完成的,而且只能解决单例作用域的bean 循环依赖
。通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到该bean,如下代码所示:
addSingletonFactory (beanName,new ObjectFactory (){
public Object getObject () throws BeansException {
return getEarlyBeanReference (beanName, mbd, bean);
}
});
具体步骤如下。
- Spring 容器创建单例
testA
bean,首先根据无参构造器创建 bean,并暴露一个ObjectFactory
用于返回一个提前暴露一个创建中的bean,并将testA
标识符放到当前创建bean 池
,然后进行 setter 注入testB。 - Spring 容器创建单例
testB
bean,首先根据无参构造器创建 bean,并暴露一个ObjectFactory
用于返回一个提前暴露一个创建中的 bean,并将testB
标识符放到当前创建 bean池
,然后进行 setter 注入testC。 - Spring 容器创建单例
testC
bean,首先根据无参构造器创建 bean,并暴露一个ObjectFactory
用于返回一个提前暴露一个创建中的 bean,并将testC
标识符放到当前创建 bean池
,然后进行 setter 注入testA。进行注入testA 时由于提前暴露了ObjectFactory
工厂,从而使用它返回提前暴露一个创建中的bean。 最后在依赖注入testB 和 testA,完成 setter 注入。
- 对于单例 bean 来说,可以通过
setAllowCircularRefernces(false)
来禁用循环引用
prototype 范围的依赖处理
对于 prototype
作用域 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存prototype 作用域的bean
,因此无法提前暴露一个创建中的 bean
如需查看相关 spring 源码解析,请查看 spring 源码解析之 doCreateBean
这里会有spring 详细解决循环依赖的源码解析 spring 源码解析之 doGetBean
附上spring 解决三级缓存的几个缓存