什么是循环依赖
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
Spring中循环依赖场景有哪些
- 构造器循环依赖
- setter循环依赖
怎么检测是否存在循环依赖
抛出了BeanCurrentlyInCreationException异常?
Spring是如何解决循环依赖的?
总结:
Spring容器会将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持
在池中。因此如果在创建bean过程中发现当前bean已经在“当前创建bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖。对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
第一种:构造器参数循环依赖
构造器参数循环依赖是无法解决的。
首先我们先初始化三个Bean。
public class StudentA {
private StudentB studentB ;
public void setStudentB(StudentB studentB) {
this.studentB = studentB;
}
public StudentA() {
}
public StudentA(StudentB studentB) {
this.studentB = studentB;
}
}
ublic class StudentB {
private StudentC studentC ;
public void setStudentC(StudentC studentC) {
this.studentC = studentC;
}
public StudentB() {
}
public StudentB(StudentC studentC) {
this.studentC = studentC;
}
}
public class StudentC {
private StudentA studentA ;
public void setStudentA(StudentA studentA) {
this.studentA = studentA;
}
public StudentC() {
}
public StudentC(StudentA studentA) {
this.studentA = studentA;
}
}
StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,
我们都把这三个Bean交给Spring容器管理,并用有参构造实例化。
<bean id="studentA" class="com.pww.student.StudentA">
<constructor-arg index="0" ref="studentB"></constructor-arg>
</bean>
<bean id="studentB" class="com.pww.student.StudentB">
<constructor-arg index="0" ref="studentC"></constructor-arg>
</bean>
<bean id="studentC" class="com.pww.student.StudentC">
<constructor-arg index="0" ref="studentA"></constructor-arg>
</bean>
下面是测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");
}
}
执行结果报错信息为:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
针对以上代码的分析如下:
- Spring容器先创建studentA,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentA标识符放到池子里,并准备创建studentA所需要的构造器参数studentB;
- Spring容器创建studentB,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentB标识符放到池子里,准备创建studentB所需要的studentC;
- Spring容器创建studentC,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentC标识符放到池子里,准备创建studentC所需要的studentA;
- 此时发现studentA已经在“当前创建bean池”里了,所以会抛出BeanCurrentlyInCreationException。
第二种:setter注入,单例
Spring是先将Bean对象实例化之后再设置对象属性的。
为了避免循环依赖,Spring采取了一种将正在创建的bean实例提早暴露加入到singletonFactories缓存中。
修改配置文件为set方式注入
<!--scope="singleton"(加不加都行,默认就是单例方式) -->
<bean id="studentA" class="com.pww.student.StudentA" scope="singleton">
<property name="studentB" ref="studentB"></property>
</bean>
<bean id="studentB" class="com.pww.student.StudentB" scope="singleton">
<property name="studentC" ref="studentC"></property>
</bean>
<bean id="studentC" class="com.pww.student.StudentC" scope="singleton">
<property name="studentA" ref="studentA"></property>
</bean>
下面是测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");
System.out.println(context.getBean("studentA", StudentA.class));
}
}
打印结果为:
com.pww.student.StudentA@1fbfd6
为什么用set方式就不报错了呢 ?针对以上代码的分析如下:
- Spring容器先创建单例bean,studentA,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentA标识符放到“当前创建bean池”里,然后setter注入studentB;
- Spring容器创建单例bean,studentB,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentB标识符放到“当前创建bean池”里,然后setter注入studentC;
- Spring容器创建单例bean,studentC,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentC标识符放到“当前创建bean池”里,然后setter注入studentA;
- setter注入studentA时,发现studentA已经提前暴露了,使用“ObjectFactory”返回正在创建中的studentA;
- 注入studentA、studentB,完成。
第三种:setter注入,原型
这种循环依赖,Spring无法解决。
修改配置文件为:
<bean id="studentA" class="com.pww.student.StudentA" scope="prototype">
<property name="studentB" ref="studentB"></property>
</bean>
<bean id="studentB" class="com.pww.student.StudentB" scope="prototype">
<property name="studentC" ref="studentC"></property>
</bean>
<bean id="studentC" class="com.pww.student.StudentC" scope="prototype">
<property name="studentA" ref="studentA"></property>
</bean>
测试用例:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");
//此时必须要获取Spring管理的实例,因为scope="prototype" ,只有请求获取的时候才会实例化对象
System.out.println(context.getBean("studentA", StudentA.class));
}
}
打印结果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'studentA': Requested bean is currently in creation: Is there an unresolvable circular reference?
为什么原型模式就报错了呢 ?
对于“prototype”作用域的bean,Spring容器无法完成依赖注入,因为“prototype”作用域的bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的bean。
总结
- 构造器参数循环依赖是无法解决的。 Spring容器会将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持
在池中。因此如果在创建bean过程中发现当前bean已经在“当前创建bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖。对于创建完毕的Bean将从“当前创建Bean池”中清除掉。 - Spring可以解决setter注入&单例bean的循环依赖。为了避免循环依赖,Spring采取了一种将正在创建的bean实例提早暴露加入到singletonFactories缓存中。
- Spring无法解决setter注入&原型bean的循环依赖。因为“prototype”作用域的bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的bean。
完。