循环依赖就是循环引用,就是两个或者多个bean之间互相持有对方,比如A引用B,B引用C,C引用A。如下图
注:此处不是循环调用,循环调用是方法之间的调用,循环调用是无法解决的,除非有终结条件,否则就是死循环,最终会导致内存溢出。
spring循环依赖包括:构造器循环依赖、setter循环依赖。
public class A {
private B b;
public void a() {
b.b();
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
public class B {
private C c;
public void b() {
c.c();
}
public C getC() {
return c;
}
public void setC(C c) {
this.c = c;
}
}
public class C {
private A a;
public void c() {
a.a();
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
在spring中循环依赖分为三种情况:
1、构造器循环依赖
表示通过构造器注入构成的循环依赖,此依赖是无法解决,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
如果在创建A类时,构造器需要B类,接着创建B类又发现需要C类,则又去创建C,最终发现又需要A类,从而形成一个环,没办法创建。
spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建bean过程中,发现自己已经在“当前创建bean池”里时,则抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean从“当前创建bean池”中清除掉。
我们通过一个测试案例来分析
(1)创建配置文件
<bean id="a" class="com.spring.test">
<constructor=arg index="0" ref="b">
</bean>
<bean id="b" class="com.spring.test">
<constructor=arg index="0" ref="c">
</bean>
<bean id="c" class="com.spring.test">
<constructor=arg index="0" ref="a">
</bean>
(2)创建测试案例
public class TestApp {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
}
}
针对以上代码分析:
a、spring创建"a" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“B”,并将a标识符放到“当前bean池”;
b、spring创建"b" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“C”,并将b标识符放到“当前bean池”;
c、spring创建"c" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“A”,并将c标识符放到“当前bean池”;d、到此为止,spring要去创建“a”bean,发现该bean标识符在“当前bean池”,表示循环依赖,抛出BeanCurrentlyInCreationException异常
2、setter循环依赖
表示通过setter注入方式构成的循环依赖,对于setter注入造成的依赖是通过spring容器提前暴露刚完成 构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,如下代码示例:
addSingletonFactory(beanName,new ObjectFactory() {
public Object getObject() throw BeansException{
return getEarlyBeanReference(beanName,mbd,bean);
}
});
具体步骤如下:
a、spring创建单例“a”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“a”标识符放到“当前创建bean池”,然后进行setter注入“b”;
b、spring创建单例“b”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“b”标识符放到“当前创建bean池”,然后进行setter注入“c”;
c、spring创建单例“c”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“c”标识符放到“当前创建bean池”,然后进行setter注入“a”时,由于提前暴露了“ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的bean;
d、最后再注入“b”、和“a”,完成setter注入。
3、prototype范围的依赖处理
对于“prototype”作用域bean,spring容器无法完成依赖注入,因为spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean,示例如下:
(1)创建配置文件
<bean id="a" class="com.spring.test" scope="prototype">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="com.spring.test" scope="prototype">
<property name="c" ref="c"/>
</bean>
<bean id="c" class="com.spring.test" scope="prototype">
<property name="a" ref="a"/>
</bean>
(2)创建测试用例
public class TestApp {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
}
}
对于“singleton”作用域的bean,可以通过“setAllowCircularReference(false);”来禁用循环引用。