理解Spring循环引用(循环依赖)

转载自:https://blog.csdn.net/chen2526264/article/details/80673598

一、介绍

循环引用,也可以叫做循环依赖,就是A类依赖了B类,B类又依赖A类,比如下面这种情况:

class A {
	private B b;
public B getB() {
	return b;
}

public void setB(B b) {
	this.b = b;
}

}

class B {
private A a;

public B(A a) {
	this.a = a;
}

public A getA() {
	return a;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在Spring容器中,可以通过依赖注入的方式,将A注入给B,同时也将B注入给A,那么这时在该容器中,A和B就存在循环依赖。当存在循环依赖的时候,某些情况下Spring可以正确处理,某些情况下,Spring会抛出异常。下面就讲解一下什么情况可以正确处理,什么情况会抛出异常。

在开始介绍各种情况之前,首先要了解以下三个知识点:

  1. Spring中依赖注入的方式有两种,属性注入与构造器注入。上面的代码中,类A就是通过属性注入的方式注入了B,类B是通过构造器注入的方式注入了A。

  2. Spring中的bean根据作用域的不同,可以大体分为两类,singleton和prototype。singleton在一个容器中,只会有一个实例;而prototype在每次调用时,都会产生一个新的实例。

  3. Spring中,单例bean有延迟加载和立即加载两种加载方式,其中立即加载模式会在容器启动的时候就创建bean,而延迟加载会在容器启动后,使用到bean的时候再加载它。本篇分析一律使用延迟加载,因为有时候单例bean的加载顺序,会影响到创建bean的成功或失败。

对于上述三点不了解的朋友,可以看一下Spring依赖Spring作用域Spring延迟加载

二、循环依赖的bean都是通过构造器注入的情况

2.1 循环依赖的bean都是singleton

示例代码

class A {
	private B b;
public B getB() {
	return b;
}

public A(B b) {
	this.b = b;
}

}

class B {
private A a;

public A getA() {
	return a;
}

public B(A a) {
	this.a = a;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

配置文件

<bean id="singletonA" class="ccc.spring.circulardependencies.constructor.A" lazy-init="true">
	<constructor-arg name="b" ref="singletonB"/>
</bean>
<bean id="singletonB" class="ccc.spring.circulardependencies.constructor.B" lazy-init="true">
	<constructor-arg name="a" ref="singletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
	new ClassPathXmlApplicationContext("ApplicationContext.xml");
A a = (A) applicationContext.getBean("singletonA");

 
 
  • 1
  • 2
  • 3

测试结果

失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'singletonA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'singletonB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'singletonB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'singletonA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singletonA': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

上面的代码中,singletonA和singletonB都是单例,并且通过构造器注入的方式依赖于对方,这种情况下获取任何一个bean的时候都会失败,Spring抛出BeanCreationException异常。

2.2 循环依赖的bean都是prototype

示例代码

和2.1节相同。

配置文件

<bean id="prototypeA" class="ccc.spring.circulardependencies.constructor.A" scope="prototype">
	<constructor-arg name="b" ref="prototypeB"/>
</bean>
<bean id="prototypeB" class="ccc.spring.circulardependencies.constructor.B" scope="prototype">
	<constructor-arg name="a" ref="prototypeA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
	new ClassPathXmlApplicationContext("ApplicationContext.xml");

A a = (A) applicationContext.getBean(“singletonA”);

  • 1
  • 2
  • 3
  • 4

测试结果

失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'prototypeA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'prototypeB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'prototypeB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'prototypeA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeA': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

该例中A和B都是prototype,并且通过构造器注入了对方。在获取bean的时候,同样抛出了BeanCreationException异常。

2.3 循环依赖的bean同时有singleton和prototype

实例代码

和2.1节相同。

配置文件

<bean id="mixSingletonA" class="ccc.spring.circulardependencies.constructor.A" lazy-init="true">
	<constructor-arg name="b" ref="mixPrototypeB"/>
</bean>
<bean id="mixPrototypeB" class="ccc.spring.circulardependencies.constructor.B"  scope="prototype">
	<constructor-arg name="a" ref="mixSingletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

先获取singleton的情况:

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

A a = (A) applicationContext.getBean(“mixSingletonA”);

  • 1
  • 2
  • 3
  • 4

先获取protytype的情况:

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

B b = (B) applicationContext.getBean(“mixPrototypeB”);

  • 1
  • 2
  • 3
  • 4

测试结果

先获取singleton的情况:

失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'mixSingletonA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixPrototypeB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mixPrototypeB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixSingletonA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mixSingletonA': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

先获取protytype的情况:

失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'mixPrototypeB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixSingletonA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mixSingletonA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixPrototypeB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mixPrototypeB': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

该例中,mixSingletonA是singleton,mixPrototypeB是prototype,在Spring容器启动后,无论是先获得谁,获取bean都会失败,并抛出BeanCreationException。

2.4 小结

当循环依赖的bean都是通过构造器注入依赖的时候,无论这些bean是singleton还是prototype,在获取bean的时候都会失败。

三、循环依赖的bean都是通过属性注入的情况

3.1 循环依赖的bean都是singleton

示例代码

class A {
	private B b;
public B getB() {
	return b;
}

public void setB(B b) {
	this.b = b;
}

}

class B {
private A a;

public A getA() {
	return a;
}

public void setA(A a) {
	this.a = a;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

配置文件

<bean id="singletonA" class="ccc.spring.circulardependencies.field.A" lazy-init="true">
	<property name="b" ref="singletonB"/>
</bean>
<bean id="singletonB" class="ccc.spring.circulardependencies.field.B" lazy-init="true">
	<property name="a" ref="singletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");
A a = (A) applicationContext.getBean("singletonA");
B b = (B) applicationContext.getBean("singletonB");
assertEquals(a, b.getA());
assertEquals(b, a.getB());

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试结果

运行成功。

该例中singletonA和singletonB都是singleton,并且通过属性注入的方式依赖对方,在Spring容器启动后,无论是先获得哪个bean,都会成功。

3.2 循环依赖的bean都是prototype

示例代码

和3.1节相同。

配置文件

<bean id="prototypeA" class="ccc.spring.circulardependencies.field.A" scope="prototype">
	<property name="b" ref="prototypeB"/>
</bean>
<bean id="prototypeB" class="ccc.spring.circulardependencies.field.B" scope="prototype">
	<property name="a" ref="prototypeA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

A a = (A) applicationContext.getBean(“prototypeA”);

  • 1
  • 2
  • 3
  • 4

测试结果

失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'prototypeA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'prototypeB' while setting bean property 'b'; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'prototypeB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'prototypeA' while setting bean property 'a'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeA': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

该例中,prototypeA和prototypeB都是protytype,并且通过属性注入的方式依赖对方,在Spring容器启动后,无论是先获得哪个bean,都会失败,并抛出BeanCreationException异常。

3.3 循环依赖的bean同时有singleton和prototype

示例代码

和3.1节相同。

配置文件

<bean id="mixSingletonA" class="ccc.spring.circulardependencies.field.A" lazy-init="true">
	<property name="b" ref="mixPrototypeB"/>
</bean>
<bean id="mixPrototypeB" class="ccc.spring.circulardependencies.field.B" scope="prototype">
	<property name="a" ref="mixSingletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

先获取singleton的情况:

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

A a = (A) applicationContext.getBean(“mixSingletonA”);
B b = (B) applicationContext.getBean(“mixPrototypeB”);
assertNotEquals(a.getB(), b);
assertEquals(b.getA(), a);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

先获取protytype的情况:

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

expectedEx.expect(BeanCreationException.class);
B b = (B) applicationContext.getBean(“mixPrototypeB”);

  • 1
  • 2
  • 3
  • 4
  • 5

测试结果

先获取singleton的情况:成功

先获取singleton的情况:失败

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'mixPrototypeB' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixSingletonA' while setting bean property 'a'; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mixSingletonA' defined in class path resource [ApplicationContext.xml]: Cannot resolve reference to bean 'mixPrototypeB' while setting bean property 'b'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mixPrototypeB': Requested bean is currently in creation: Is there an unresolvable circular reference?

 
 
  • 1
  • 2
  • 3
  • 4

该例中,mixSingletonA是singleton,mixPrototypeB是prototype,并且都通过属性注入的方式依赖的对方。

当容器启动后,如果先获取的是singleton,那么两个bean都能成功获得。但是如果先获取的是prototype,那么就会失败。注意本例中的mixSingletonA是延迟加载的,所以只有在真正获取该bean的时候Spring容器才会去创建bean。

3.4 小结

当循环依赖的bean都是通过属性注入依赖的时候,根据bean的作用域是singleton还是prototpye,会有不同的表现。

  • 如果循环依赖的bean都是singleton,那么无论先获取哪个bean,都能成功。
  • 如果循环依赖的bean都是prototype,那么无论先获取哪个bean,都会失败。
  • 如果循环依赖的bean中有singleton,也有prototype,那么当先获取的那个bean是singleton时,就会成功,否则失败。

四、循环依赖的bean同时有属性注入和构造器注入的情况

当循环依赖的bean同时有属性注入和构造器注入的时候,情况有点多,就不一一详细,这里只介绍可以运行成功的情况。

4.1 循环依赖的bean都是singleton,且在容器启动后先去获取通过属性注入依赖的bean

实例代码

class A {
	private B b;
public B getB() {
	return b;
}

public A(B b) {
	this.b = b;
}

}

class B {
private A a;

public A getA() {
	return a;
}

public void setA(A a) {
	this.a = a;
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

配置文件

<bean id="singletonA" class="ccc.spring.circulardependencies.mix.A" lazy-init="true">
	<constructor-arg name="b" ref="singletonB"/>
</bean>
<bean id="singletonB" class="ccc.spring.circulardependencies.mix.B" lazy-init="true">
	<property name="a" ref="singletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");
B b = (B) applicationContext.getBean("singletonB");
A a = (A) applicationContext.getBean("singletonA");
assertEquals(a, b.getA());
assertEquals(b, a.getB());

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试结果

成功

该例中singletonA和singletonB都是单例,但是singletonA是通过构造器注入的singletonB,而singletonB是通过属性注入的singletonA。当Spring容器启动后,如果先获取singletonB就会成功。

4.2 循环依赖的bean有singleton和prototype,其中singleton是通过属性注入依赖,prototype是通过构造器注入依赖。在容器启动后先获取singleton。

实例代码

和4.1节相同。

配置文件

<bean id="mixPrototypeA" class="ccc.spring.circulardependencies.mix.A" scope="prototype">
	<constructor-arg name="b" ref="mixSingletonB"/>
</bean>
<bean id="mixSingletonB" class="ccc.spring.circulardependencies.mix.B" lazy-init="true">
	<property name="a" ref="mixPrototypeA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试代码

ApplicationContext applicationContext =
		new ClassPathXmlApplicationContext("ApplicationContext.xml");

B b = (B) applicationContext.getBean(“mixSingletonB”);
A a = (A) applicationContext.getBean(“mixPrototypeA”);
assertNotEquals(a, b.getA());

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试结果

成功

该例中mixPrototypeA是prototype,mixSingletonB都是singleton,但是mixPrototypeA是通过构造器注入的mixSingletonB,而mixSingletonB是通过属性注入的mixPrototypeA。当Spring容器启动后,如果先获取mixSingletonB就会成功。

五、结论

通过上面三节的例子来看,可以得出以下结论:

  1. 如果循环依赖的bean都是通过构造器注入依赖,那么不管它们是singleton还是prototype,都会失败。
  2. 如果循环依赖的bean都是prototype,那么不管它们是通过构造器注入依赖还是通过属性注入依赖,都会失败。
  3. 如果循环依赖的bean既有构造器注入也有属性注入,既有singleton也有prototype,在容器启动后,只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功,别的情况都会失败。

所以最终结论就是:

如果多个bean存在循环依赖,在Spring容器启动后,只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功,别的情况都会失败

六、分析

循环引用的bean之间必然会构成一个环,如下图所示,A、B、C之间构成了一个环形。

这里写图片描述

当Spring容器在创建A时,会发现其引用了B,从而会先去创建B。同样的,创建B时,会先去创建C,而创建C时,又先去创建A。最后A、B、C之间互相等待,谁都没法创建成功。

要想打破这个环,那么这个环中至少需要有一个bean可以在自身的依赖还没有得到满足前,就能够被创建出来(最起码要被实例化出来,可以先不注入其需要的依赖)。这种bean只能是通过属性注入依赖的类,因为它们可以先使用默认构造器创建出实例,然后再通过setter方法注入依赖。而通过构造器注入依赖的类,在它的依赖没有被满足前,无法被实例化。而且这个bean,还必须是singleton,不能是prototype。至于为什么不能是prototype呢,我们一起来回顾一下3.1节的例子:

3.1节中类A和类B都是通过属性注入依赖,并且他们都是singleton,配置文件如下:

<bean id="singletonA" class="ccc.spring.circulardependencies.field.A" lazy-init="true">
	<property name="b" ref="singletonB"/>
</bean>
<bean id="singletonB" class="ccc.spring.circulardependencies.field.B" lazy-init="true">
	<property name="a" ref="singletonA"/>
</bean>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Spring容器启动后,如果我们去获取singletonA,那么容器的操作步骤大致如下:

  1. 尝试创建bean singletonA,发现singletonA是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个A的实例,并保存对它的引用,并且将singletonA标记为“正在创建中的singleton”。然后发现singletonA依赖了singletonB,所以尝试创建singletonB。
  2. 尝试创建bean singletonB,发现singletonB是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个B的实例,并保存对它的引用,并且将singletonB标记为“正在创建中的singleton”。然后发现singletonB依赖了singletonA,所以尝试创建singletonA。
  3. 尝试创建singletonA,注意,这时Spring容器发现singletonA“正在创建中”,那么就不会再去创建singletonA,而是返回容器之前保存了的对singletonA的引用。
  4. 容器将singletonA通过setter方法注入到singletonB,从而singletonB完成创建。
  5. 容器将singletonB通过setter方法注入到singletonA,从而singletonA完成创建。

上述步骤,最重要的是第1步和第3步。在第1步中,容器会保存对singletonA的引用,在第3步中,再返回对singletonA的引用,从而可以成功创建那些依赖了singletonA的bean(本例中是singletonB)。这样,循环依赖的环就在singletonA这个点这里被打破。

那为什么prototype不能成为打破这个环的一个点呢?原因就在于Spring容器只会对singleton保存引用,而对于prototype,并不会对其保存引用,这就导致在第3步中并不能获得之前创建的bean(因为引用不到它)。

至于为什么容器不对prototype保存引用,那就涉及到singleton和portotpye的概念,如果也对prototype保存引用,那么其实它就变成了singleton。可以看一下Spring作用域

按道理,在循环依赖的环里,只要有一个bean,是通过属性注入依赖,并且是singleton,那么这个环就可以被打破,无论获取他们的顺序是怎样。但是我们在第五节得出过结论“只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功”,为什么会这样呢?这就和Spring的实现有关了,当Spring容器遍历那些循环依赖的bean时,只要遍历到那种已经遍历过一次的bean,并且它们不是通过属性注入依赖的singleton时,就会直接抛出BeanCurrentlyInCreationException异常。

参考:https://blog.csdn.net/u010723709/article/details/47185959

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值