Spring循环依赖问题

前言

最近对Spring的循环依赖比较感兴趣,特地研究了一下并分享给大家。

要说循环依赖,先理解循环引用,如果一个Class如TestA,需要引用TestB,而同时TestB又引用TestA,则可以称这两个类循环引用。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestA {
private TestB testB;
public TestA() {
}
public TestA(TestB testB) {
this.testB = testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
public TestB getTestB() {
return testB;
}
}
public class TestB {
private TestA testA;
public TestB() {
}
public TestB(TestA testA) {
this.testA = testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
public TestA getTestA() {
return testA;
}
}

如果这两个类被Spring管理,成为Spring的Bean,就会有循环依赖问题。

对于循环依赖问题,可能会有问题导致Bean创建不成功,我们来看一下。

正文

我们知道,对于Spring的Bean,有singleton(单例)和prototype(多例)两种模式。

对于SpringBean的创建,我们有构造器注入和Setter注入两种方式。

它们都会对循环依赖问题造成影响,我们分别讨论。

Bean都为singleton模式,都通过构造器注入

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
@Autowired
public TestA(TestB testB) {
this.testB = testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
@Autowired
public TestB(TestA testA) {
this.testA = testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们使用下面的测试类观测Bean创建情况,后面的都可以使用这个测试类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Test
public void test(){
try{
TestA a = SpringUtils.getBean(TestA.class);
System.out.println(a);
}catch (Exception e){
e.printStackTrace();
}
try{
TestB b = SpringUtils.getBean(TestB.class);
System.out.println(b);
}catch (Exception e){
e.printStackTrace();
}
}
}

对于上面的TestA和TestB我们运行后发现出现异常,Bean创建不成功,部分异常如下:

1
2
3
...
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?
...

Bean都为prototype模式,都通过构造器注入

我们再看看把Bean改为prototype模式后如何。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
@Autowired
public TestA(TestB testB) {
this.testB = testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
@Autowired
public TestB(TestA testA) {
this.testA = testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}

经过测试可以发现也是不成功的,抛出BeanCurrentlyInCreationException异常。

其实上面两个例子是比较好理解的,通过构造器注入,也就是Bean TestA在创建的时候就需要TestB,TestB在创建的时候就需要TestA,显然,Spring无法解决这种情况,我们也无法解决这种情况。

Bean都为singleton模式,都通过Setter注入

我们再来看下这种情况,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
public TestA(TestB testB) {
this.testB = testB;
}
@Autowired
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
public TestB(TestA testA) {
this.testA = testA;
}
@Autowired
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们测试一下可以发现运行成功了,成功输出两个Bean对象信息。

1
2
com.zwt.demo.circularreference.TestA@2cc04358
com.zwt.demo.circularreference.TestB@68b58644

其实这种情况也是比较好理解的:

  • 当Bean TestA创建的时候需要TestB,TestB会创建一个默认的使用无参构造器的Bean对象,此时TestB里的TestA为空,我们称为TestB(空);
  • TestA使用TestB(空)完成依赖注入,生成Bean TestA;
  • TestA构造完成,其返回给TestB(空),这时候TestB里的TestA不在为空,TestB构造完成。

其过程犹如如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//1.生成1个TestB对象
TestB testB = new TestB();
//2.将TestB注入到TestA
TestA testA = new TestA();
testA.setTestB(testB);
//3.将TestA注入到TestB
testB.setTestA(testA);
//4.最后两者完成循环依赖
System.out.println(testA.getTestB());
System.out.println(testB.getTestA());
}

其实可以看到最关键的还是TestA、TestB的无参构造函数,其实这个无参构造函数指的是无TestA或者TestB参与的构造函数,这样初始化实例时不涉及循环依赖类,而在创建成功后通过参数注入。

我们把上面TestA和TestB的无参构造函数删去,只保留有参构造,测试运行可以看到出现了BeanCurrentlyInCreationException异常。

Bean都为prototype模式,都通过Setter注入

这种情况貌似也可以??我们来看下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
public TestA(TestB testB) {
this.testB = testB;
}
@Autowired
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
public TestB(TestA testA) {
this.testA = testA;
}
@Autowired
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们测试后发现它也抛出BeanCurrentlyInCreationException异常,证明是不可以的。

这种情况如何理解呢?我先上段代码:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
TestB testB1 = new TestB();
TestA testA1 = new TestA();
testA1.setTestB(testB);
testB1.setTestA(testA);
System.out.println(testA1.getTestB());
System.out.println(testB1.getTestA());
}

我们明显看到上述代码是错的,明显不存在testB和testA对象,testA1和testB1如何set?

是的,Spring就是这样,对于prototype(多例)模式,它不会保存已经创建的TestA或者TestB对象的引用。

也就是在注入时,我们可以认为完成了若干个(多例)TestA,但是都是“半成品”(没有TestB的注入),这时候TestB(多例)创建,需要TestA,即使拿到TestA(空),也无法将创建好的TestB反作用于TestA(空),使其成为TestA。

我们来看下Spring相关源码。

我们在AbstractBeanFactorydoGetBean方法看起,如下图:

upload successful

可以看到拿Bean前会先检查单例Bean,如果拿到了sharedInstance并且参数为空,并判断这个Singleton是不是正在被创建中,如果是的话那么就存在循环引用,拿到的是个Bean“半成品”。

如果拿不到,它就是个多例,判断下是不是正在创建中,是的话就直接抛出异常了。

再来看下调用的DefaultSingletonBeanRegistry类的getSingleton方法。

upload successful

可以看到这种Bean都会保存在一个叫earlySingletonObjectsHashMap里,如果没有就尝试去一个叫singletonFactoriesHashMap里去获取。

然后可以看到这个HashMap的值是通过addSingletonFactory方法获得的。

upload successful

继续跟踪可以看到放入条件:单例、允许循环依赖、当前Bean正在被创建,如下图:

upload successful

最后getEarlyBeanReference返回了一个比较特殊的Object。

upload successful

我们也可以通过debug观察代码运行情况,这儿不再过多叙述。

Bean一个为prototype模式,一个为singleton模式,都通过构造器注入

这个根据我们上面等判断应该是不可以的。我们看到代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
@Autowired
public TestA(TestB testB) {
this.testB = testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
@Autowired
public TestB(TestA testA) {
this.testA = testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们可以测试同样抛出异常,实际上如果循环依赖的Bean都是通过构造器注入的,那么无论如何都是创建不成功的,与Bean创建顺序和Bean类型没有任何关系。

Bean一个为prototype模式,一个为singleton模式,都通过Setter注入

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
public TestA(TestB testB) {
this.testB = testB;
}
@Autowired
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
public TestB(TestA testA) {
this.testA = testA;
}
@Autowired
public void setTestA(TestA testA) {
this.testA = testA;
}
}

输出日志:

1
2
com.zwt.demo.circularreference.TestA@327ed9f5
com.zwt.demo.circularreference.TestB@67594471

我们可以看到这种情况下运行成功,TestA或者TestB有一个为Singleton的,通过参数注入,就能成功创建Bean。

Bean一个为prototype模式,通过构造器注入,一个为singleton模式,通过Setter注入

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
public TestA(TestB testB) {
this.testB = testB;
}
@Autowired
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
@Autowired
public TestB(TestA testA) {
this.testA = testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们可以看到运行成功,单例的TestA会先创建,名为TestA(空),然后创建TestB,使用了TestA(空),TestB创建成功后,TestA(空)里的testB被属性赋值。

Bean一个为prototype模式,通过Setter注入,一个为singleton模式,通过构造器注入

这种情况根据上面经验,应该是不可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
@Component
public class TestA {
private TestB testB;
public TestA() {
}
@Autowired
public TestA(TestB testB) {
this.testB = testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class TestB {
private TestA testA;
public TestB() {
}
public TestB(TestA testA) {
this.testA = testA;
}
@Autowired
public void setTestA(TestA testA) {
this.testA = testA;
}
}

我们测试一下,确实也是不可以的,抛出BeanCurrentlyInCreationException异常。

这种情况可以认为TestA(单例)创建需要TestB,但是TestB是多例的,Spring中无法形成TestB的唯一引用作用于TestA(形成了TestB就是单例的了),也就无法创建TestA Bean。

总结

上面说了很多情况,我们简单总结下。

对于TestA和TestB两个类,如果存在循环依赖:

TestATestBSpring创建结果
Singleton模式
Constructor注入
Singleton模式
Constructor注入
失败
Prototype模式
Constructor注入
Prototype模式
Constructor注入
失败
Singleton模式
Setter注入
Singleton模式
Setter注入
成功
Prototype模式
Setter注入
Prototype模式
Setter注入
失败
Singleton模式
Constructor注入
Prototype模式
Constructor注入
失败
Prototype模式
Constructor注入
Singleton模式
Constructor注入
同上(失败)
Singleton模式
Setter注入
Prototype模式
Setter注入
成功
Prototype模式
Setter注入
Singleton模式
Setter注入
同上(成功)
Prototype模式
Constructor注入
Singleton模式
Setter注入
成功
Singleton模式
Setter注入
Prototype模式
Constructor注入
同上(成功)
Singleton模式
Constructor注入
Prototype模式
Setter注入
失败
Prototype模式
Setter注入
Singleton模式
Constructor注入
同上(失败)

以上列举了所有情况,根据表格我们可以看出创建成功的,一定至少有一个Bean为Singleton模式且Setter注入。

这个结论也与我们刚才分析的一部分Spring源码是一致的,首先构造一个未完全初始化的Bean,这个Bean要求为单例的,而后通过实例化另一个循环依赖Bean,成功后通过Setter完成原来单例Bean的初始化。

以上就是Spring循环依赖的全部内容。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道