1.序言
什么是循环依赖?
怎么解决循环依赖?
解决方式的原理是什么?
2.什么是循环依赖?
上一段最直观的代码吧!
public class A {
public A() {new B();}
}
public class B {
public B() {new A();}
}
public static void main(Stirng[] args) {
new A();
}
看到了吧,这就是循环依赖!即A类构造器中返回一个B类的实例,B类构造器中返回一个A类的实例!
简单来说,就是你中有我,我中有你!
但是除了上述的构造器依赖,还有一种依赖方式,及set属性注入的依赖方式。
public class AService {
private BService b;
public void setB(Bservice b) {
this.b = b;
}
}
public class BService {
private AService a;
public void setA(Aservice a) {
this.a = a;
}
}
<bean id="aService" class="com.ssm.TestApplication.AService">
<property name="b" ref="bService"/>
</bean>
<bean id="bService" class="com.ssm.TestApplication.BService">
<property name="a" ref="aService"/>
</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?
报错原因:
-
Spring首先注入 AService 是首先会将该Bean的name放入inCreationCheckExclusions 和 singletonsCurrentlyInCreation中 ,当注入时发现有一个 b属性,然后会就会到IOC中查找 Bservice 的引用
-
若有Bservice引用则直接赋值给b属性,否则会去创建BSerivce 这个Bean,在本例中由于是IOC初始化所以会去实例化BSerivce.
-
当去实例化BService时,发现有个 a属性,然后就又会去查找 AService引用,由于第一步还没有结束,所以IOC是没有ASerivce的。然后去尝试注入AService,但是Spring注入前有一个对inCreationCheckExclusions 和 singletonsCurrentlyInCreation有一个校验,会判断这两个缓存中是否包含 ASerivce的beanName。若有则直接抛异常。
个人理解就是:A实例还没有实例化完成并进入到spring的IOC容器中,所以B实例在实例化时,找不到A类的实例,就报错了!
但是如果,使用第二段代码的方式,再运行,就不会报错。纳尼?
小盆友,你是否有很多问号?
但是,各单位注意!
如果你在类上使用@prototype注解或是在配置文件bean配置中,使用scope=“prototype”,那么你就完蛋啦!
你会发现,第二段代码的方式,也会报错了!
不要急,咱们一路向北。。
3.如何解决循环依赖?
解决方式,其实上面已经提前告知了!
说说我个人的想法吧!
第一种,尽量避免使用循环依赖的方式获取要用到的对象。我不用,你怎么错。
第二种,使用单例的set属性注入的方式,来注入依赖对象。
4.解决循环依赖的方式的底层原理是什么?
下面来扒一扒为什么我上面说的第二钟解决方案可以避免循环依赖报错呢?原理为何?
这里我们需要了解两个点:
spring中bean的生命周期 和 spring三级缓存
在我们使用第二种解决方式时,spring创建A或B实例对象时,其实是提前通过三级缓存将依赖的对象实例存入了spring容器当中,所以在循环依赖时,不存在找不到对象实例而报错了 !但是,有一点需要注意,三级缓存获取对象时,是要先执行构造器方法的,所以这也就是为什么构造器方式的循环依赖会报错!