循环依赖就是循环引用,每个类中嵌套引用,在spring中表现为两个或者多个bean相互之间持有对方,比如A引用B,B引用C,C又引用A,最终反映出来形成一个环。循环调用是无法解决的,一定要有终止条件才可以,否则就是死循环,最终的结果就是内存溢出。
Spring解决循环依赖的方法:
Spring容器循环依赖包括构造器循环依赖和setter循环依赖,首先构造出一个循环依赖的条件,模拟一下,利用Spring提供的方法试着解决和发现问题。
首先创建三个bean,TestA、TestB、TestC
TestA:
public class TestA {
private TestB testB;
public TestA(TestB testB) {
this.testB = testB;
}
public TestA() {
}
public TestB getTestB() {
return testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
TestB:
public class TestB {
private TestC testC;
public TestB(TestC testC) {
this.testC = testC;
}
public TestB() {
}
public TestC getTestC() {
return testC;
}
public void setTestC(TestC testC) {
this.testC = testC;
}
}
TestC:
public class TestC {
private TestA testA;
public TestC(TestA testA) {
this.testA = testA;
}
public TestC() {
}
public TestA getTestA() {
return testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}
1. 构造器循环依赖
表示通过构造器注入构成的循环依赖,这种依赖是无法解决的,只能抛出BeanCreationException
异常表示循环依赖。
程序在创建TestA时,构造器需要TestB,去创建TestB,TestB中的构造器又需要TestC,又去创建TestC,TestC的构造器又需要TestA,形成一个环,没有办法创建。
创建配置文件:
<bean id="a" class="com.bean.test.TestA">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.bean.test.TestB">
<constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.bean.test.TestC">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>
bean之间的关系如下图:
创建测试:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IDEA
*
* @author duzhentong
* @Date 2018/5/23
* @Time 21:15
*/
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/bean/test/applicationContext.xml");
System.out.println(context.getBean("a", TestA.class));
}
}
程序抛出异常:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException:……
分析构造器循环依赖。Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”,bean标识符在创建过程中将一直保持在这个池子中,因此在创建bean的时候发现自己已经在这个池子中(创建TestA时,发现需要TestB,TestB又需要TestC,TestC又需要TestA,需要注意的是,这时候TestC需要的TestA并没有创建完成,TestA还在等着后面需要的bean的成功创建他才可以成功创建)
发现这种情况时程序就会抛出BeanCreationException
异常,注意池子中装的是正在创建的bean,对于创建完毕后的bean就会从这个池子中移除。
2. setter循环依赖
修改配置文件:
<bean id="a" class="com.bean.test.TestA" scope="singleton">
<property name="testB" ref="b"></property>
</bean>
<bean id="b" class="com.bean.test.TestB" scope="singleton">
<property name="testC" ref="c"></property>
</bean>
<bean id="c" class="com.bean.test.TestC" scope="singleton">
<property name="testA" ref="a"></property>
</bean>
执行测试类,没有抛出异常,正常输出
com.bean.test.TestA@37760a63
setter方法也是Spring中的默认解决方法,可以解决循环依赖的问题。具体实现原理是:setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但并未完成其他步骤的bean完成。
Spring是先将Bean对象实例化之后再设置对象属性的
Spring先是用构造实例化Bean对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,当Spring实例化了TestA、TestB、TestC后,紧接着会去设置对象的属性,此时TestA依赖TestB,就会去Map中取出存在里面的单例TestB对象,以此类推
3. prototype范围的依赖处理
修改配置文件:
<bean id="a" class="com.bean.test.TestA" scope="prototype">
<property name="testB" ref="b"></property>
</bean>
<bean id="b" class="com.bean.test.TestB" scope="prototype">
<property name="testC" ref="c"></property>
</bean>
<bean id="c" class="com.bean.test.TestC" scope="prototype">
<property name="testA" ref="a"></property>
</bean>
执行测试类,程序还是抛出了BeanCreationException
异常
对于“prototype”作用域bean,Spring容器无法完成依赖注入,因为“prototype”作用域的bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。