Spring如何解决循环依赖
对自己说的话
深入spring原理对现在的自己来说确定太难了,但是要坚持,点滴的积累,一定会有收获的~加油!
什么是循环依赖
所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:
根据创建对象的方式不同, 分为三种情况:
- 第一种:构造器参数循环依赖
- 第二种:setter方式单例,默认方式
- 第三种:setter方式原型,prototype,也就是多例
第一种:构造器参数循环依赖
Spring容器会将每一个正在创建的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;
}
}
public 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;
}
}
上面是很基本的3个类,,StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,
我们都把这三个Bean交给Spring管理,并用 有参构造实例化对象到容器中
<bean id="a" class="com.zuoyueer.StudentA">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.zuoyueer.StudentB">
<constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.zuoyueer.StudentC">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>
下面的测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/zuoyueer/applicationContext.xml");
System.out.println(context.getBean("a", StudentA.class));
}
}
执行结果报错信息为:
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池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentB",并将"StudentA “标识符放置到"当前创建bean池”.
-
Spring容器创建"StudentB" bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentC",并将"StudentB"标识符放置到"当前创建bean池".
-
Spring容器创建"StudentC" bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentA “,并将"StudentC"标识符放置到"当前创建bean池”.
-
到此为止Spring容器要再次去创建"StudentA " bean,发现该bean标识符在"当前创建bean池"中,因为表示循环依赖,故抛出BeanCurrentlyInCreationExpection异常。
因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)
第二种:setter方式单例,默认方式
修改配置文件为set方式注入,scope="singleton"
表示单例,默认就是单例,可以不设置
<!--scope="singleton"(默认就是单例方式) -->
<bean id="a" class="com.zuoyueer.StudentA" scope="singleton">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zuoyueer.StudentB" scope="singleton">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zuoyueer.StudentC" scope="singleton">
<property name="studentA" ref="a"></property>
</bean>
下面是测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/zuoyueer/applicationContext.xml");
System.out.println(context.getBean("a", StudentA.class));
}
}
打印结果为:(正常的创建了对象,没有异常)
com.zuoyueer.StudentA@F646AE
分析原因:
- 容器安装配置顺序,先去实例化"StudentA",首先会根据无参构造器创建一个bean,并储存该对象的引用储存到容器中, 这个引用是"StudentA对象的一个早期的引用(early reference), 注意: 此时setter注入器还未被调用,这个对象是没有给属性赋值的.
- 再去实例化"StudentB",也会根据无参构造器创建一个bean,并把对象的早期的引用(early reference)储存到容器中.
- 再去实例化"StudentC", 因为"StudentC"依赖于"“StudentA” , 这时容器中有"StudentA"的对象的一个早期的引用(early reference),那么就调用setter注入器, 把"StudentA"对象的早期引用注入到"StudentC"对象中
- 以上过程是递归完成是,所以"StudentC"实例化之后,会接着调用"StudentB"对象的setter注入器,最后调用"StudentA"对象的setter注入器,完成对象中属性的赋值.也就是依赖注入,
为了理解这个早期的引用(early reference),我们需要了解Spring的三级缓存
缓存 | 用途 |
---|---|
singletonObjects | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 |
earlySingletonObjects | 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 |
singletonFactories | 存放 bean 工厂对象,用于解决循环依赖 |
实际上, 根据无参构造器创建一个bean,并将此类对应的ObjectFactory暴露出去,也就是注册到singletonFactories中, 从而使其他的bean能引用到该bean.当你依赖到了该Bean而单例缓存里面有没有该Bean的时候就会调用该工厂方法生产Bean,此时setter注入器还未被调用
为什么不把Bean直接暴露出去,而是暴露个Factory呢?因为有些Bean是需要被代理的.
以上过程最关键的一点是Spring是先将Bean对象实例化(无参构造)之后再设置对象属性的
关于3级缓存,推荐一个博主的博客:https://blog.csdn.net/f641385712/article/details/92801300
第三种:setter方式原型,prototype,也就是多例
修改配置文件为:```scope=“prototype”``表示多例,也就是每次请求的时候都创建一个新的对象
<bean id="a" class="com.zuoyueer.StudentA" scope="prototype">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zuoyueer.StudentB" scope="prototype">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zuoyueer.StudentC" scope="prototype">
<property name="studentA" ref="a"></property>
</bean>
打印结果:
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?
分析原因:
过程和第二种方式差不多,只不过,在调用setter注入器的时候,在缓存中找不到被暴露出来的工厂对象(找不到依赖的早期引用).
这是因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
多例模式是几乎不会用到的,在用的时候也要避免循环依赖, 并且对于"singleton"作用于的bean,我们可以通过"setAllowCircularReferences(false)"来禁止循环引用.
总结:
对于Spring循环依赖的情况总结如下:
不能解决的情况:
- . 构造器注入循环依赖
- . prototype field属性注入循环依赖(多例)
能解决的情况:
- . field属性注入(setter方法注入)循环依赖(单例)
参考资料:
https://blog.csdn.net/u010644448/article/details/59108799
https://blog.csdn.net/Taylar_where/article/details/90612724
https://www.imooc.com/article/34150
上一篇: 小程序-实现自定义动画弹框/提示框