spring:我是如何解決循環(huán)依賴的?
1.由同事拋的一個(gè)問題開始
最近項(xiàng)目組的一個(gè)同事遇到了一個(gè)問題,問我的意見,一下子引起的我的興趣,因?yàn)檫@個(gè)問題我也是第一次遇到。平時(shí)自認(rèn)為對(duì)spring循環(huán)依賴問題還是比較了解的,直到遇到這個(gè)和后面的幾個(gè)問題后,重新刷新了我的認(rèn)識(shí)。
我們先看看當(dāng)時(shí)出問題的代碼片段:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
這兩段代碼中定義了兩個(gè)Service類:TestService1和TestService2,在TestService1中注入了TestService2的實(shí)例,同時(shí)在TestService2中注入了TestService1的實(shí)例,這里構(gòu)成了循環(huán)依賴。
只不過,這不是普通的循環(huán)依賴,因?yàn)門estService1的test1方法上加了一個(gè)@Async注解。
大家猜猜程序啟動(dòng)后運(yùn)行結(jié)果會(huì)怎樣?
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
報(bào)錯(cuò)了。。。原因是出現(xiàn)了循環(huán)依賴。
「不科學(xué)呀,spring不是號(hào)稱能解決循環(huán)依賴問題嗎,怎么還會(huì)出現(xiàn)?」
如果把上面的代碼稍微調(diào)整一下:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
把TestService1的test1方法上的@Async注解去掉,TestService1和TestService2都需要注入對(duì)方的實(shí)例,同樣構(gòu)成了循環(huán)依賴。
但是重新啟動(dòng)項(xiàng)目,發(fā)現(xiàn)它能夠正常運(yùn)行。這又是為什么?
帶著這兩個(gè)問題,讓我們一起開始spring循環(huán)依賴的探秘之旅。
2.什么是循環(huán)依賴?
循環(huán)依賴:說(shuō)白是一個(gè)或多個(gè)對(duì)象實(shí)例之間存在直接或間接的依賴關(guān)系,這種依賴關(guān)系構(gòu)成了構(gòu)成一個(gè)環(huán)形調(diào)用。
第一種情況:自己依賴自己的直接依賴

第二種情況:兩個(gè)對(duì)象之間的直接依賴

第三種情況:多個(gè)對(duì)象之間的間接依賴

前面兩種情況的直接循環(huán)依賴比較直觀,非常好識(shí)別,但是第三種間接循環(huán)依賴的情況有時(shí)候因?yàn)闃I(yè)務(wù)代碼調(diào)用層級(jí)很深,不容易識(shí)別出來(lái)。
3.循環(huán)依賴的N種場(chǎng)景
spring中出現(xiàn)循環(huán)依賴主要有以下場(chǎng)景:

單例的setter注入
這種注入方式應(yīng)該是spring用的最多的,代碼如下:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
這是一個(gè)經(jīng)典的循環(huán)依賴,但是它能正常運(yùn)行,得益于spring的內(nèi)部機(jī)制,讓我們根本無(wú)法感知它有問題,因?yàn)閟pring默默幫我們解決了。
spring內(nèi)部有三級(jí)緩存:
singletonObjects 一級(jí)緩存,用于保存實(shí)例化、注入、初始化完成的bean實(shí)例 earlySingletonObjects 二級(jí)緩存,用于保存實(shí)例化完成的bean實(shí)例 singletonFactories 三級(jí)緩存,用于保存bean創(chuàng)建工廠,以便于后面擴(kuò)展有機(jī)會(huì)創(chuàng)建代理對(duì)象。

@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}

ObjectFactory對(duì)象。說(shuō)白了,兩次從三級(jí)緩存中獲取都是ObjectFactory對(duì)象,而通過它創(chuàng)建的實(shí)例對(duì)象每次可能都不一樣的。
ObjectFactory對(duì)象,直接保存實(shí)例對(duì)象不行嗎?AbstractAutowireCapableBeanFactory類doCreateBean方法的這段代碼中:
getEarlyBeanReference方法獲取代理對(duì)象,其實(shí)底層是通過AbstractAutoProxyCreator類的getEarlyBeanReference生成代理對(duì)象。多例的setter注入
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
AbstractApplicationContext類的refresh方法中告訴了我們答案,它會(huì)調(diào)用finishBeanFactoryInitialization方法,該方法的作用是為了spring容器啟動(dòng)的時(shí)候提前初始化一些bean。該方法的內(nèi)部又調(diào)用了preInstantiateSingletons方法
SCOPE_PROTOTYPE類型的類,非單例,不會(huì)被提前初始化bean,所以程序能夠正常啟動(dòng)。@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
}
Requested bean is currently in creation: Is there an unresolvable circular reference?
構(gòu)造器注入
@Service
public class TestService1 {
public TestService1(TestService2 testService2) {
}
}
@Service
public class TestService2 {
public TestService2(TestService1 testService1) {
}
}
Requested bean is currently in creation: Is there an unresolvable circular reference?

單例的代理對(duì)象setter注入
@Async注解的場(chǎng)景,會(huì)通過AOP自動(dòng)生成代理對(duì)象。@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.


@Service
publicclass TestService6 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}

DependsOn循環(huán)依賴
@DependsOn注解。@DependsOn(value = "testService2")
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@DependsOn(value = "testService1")
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
Circular depends-on relationship between 'testService2' and 'testService1'
@DependsOn注解是沒問題的,反而加了這個(gè)注解會(huì)出現(xiàn)循環(huán)依賴問題。AbstractBeanFactory類的doGetBean方法的這段代碼中:
4.出現(xiàn)循環(huán)依賴如何解決?

生成代理對(duì)象產(chǎn)生的循環(huán)依賴
使用 @Lazy注解,延遲加載使用 @DependsOn注解,指定加載先后關(guān)系修改文件名稱,改變循環(huán)依賴類的加載順序
使用@DependsOn產(chǎn)生的循環(huán)依賴
@DependsOn注解循環(huán)依賴的地方,迫使它不循環(huán)依賴就可以解決問題。多例循環(huán)依賴
構(gòu)造器循環(huán)依賴
@Lazy注解解決。有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
