1.由同事拋的一個問題開始
最近項目組的一個同事遇到了一個問題,問我的意見,一下子引起的我的興趣,因爲這個問題我也是第一次遇到。平時自認爲對spring循環依賴問題還是比較瞭解的,直到遇到這個和後面的幾個問題後,重新刷新了我的認識。
我們先看看當時出問題的代碼片段:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
這兩段代碼中定義了兩個Service類:TestService1
和TestService2
,在TestService1中注入了TestService2的實例,同時在TestService2中注入了TestService1的實例,這裏構成了循環依賴
。
只不過,這不是普通的循環依賴,因爲TestService1的test1方法上加了一個@Async
註解。
大家猜猜程序啓動後運行結果會怎樣?
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.
報錯了。。。原因是出現了循環依賴。
「不科學呀,spring不是號稱能解決循環依賴問題嗎,怎麼還會出現?」
如果把上面的代碼稍微調整一下:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
把TestService1的test1方法上的@Async
註解去掉,TestService1
和TestService2
都需要注入對方的實例,同樣構成了循環依賴。
但是重新啓動項目,發現它能夠正常運行。這又是爲什麼?
帶着這兩個問題,讓我們一起開始spring循環依賴的探祕之旅。
2.什麼是循環依賴?
循環依賴:說白是一個或多個對象實例之間存在直接或間接的依賴關係,這種依賴關係構成了構成一個環形調用。
第一種情況:自己依賴自己的直接依賴
第二種情況:兩個對象之間的直接依賴
第三種情況:多個對象之間的間接依賴
前面兩種情況的直接循環依賴比較直觀,非常好識別,但是第三種間接循環依賴的情況有時候因爲業務代碼調用層級很深,不容易識別出來。
3.循環依賴的N種場景
spring中出現循環依賴主要有以下場景:
單例的setter注入
這種注入方式應該是spring用的最多的,代碼如下:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
這是一個經典的循環依賴,但是它能正常運行,得益於spring的內部機制,讓我們根本無法感知它有問題,因爲spring默默幫我們解決了。
spring內部有三級緩存:
-
singletonObjects 一級緩存,用於保存實例化、注入、初始化完成的bean實例 -
earlySingletonObjects 二級緩存,用於保存實例化完成的bean實例 -
singletonFactories 三級緩存,用於保存bean創建工廠,以便於後面擴展有機會創建代理對象。
@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
對象。說白了,兩次從三級緩存中獲取都是
ObjectFactory
對象,而通過它創建的實例對象每次可能都不一樣的。
ObjectFactory
對象,直接保存實例對象不行嗎?
AbstractAutowireCapableBeanFactory
類
doCreateBean
方法的這段代碼中:
getEarlyBeanReference
方法獲取代理對象,其實底層是通過
AbstractAutoProxyCreator
類的
getEarlyBeanReference
生成代理對象。
多例的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
方法中告訴了我們答案,它會調用
finishBeanFactoryInitialization
方法,該方法的作用是爲了spring容器啓動的時候提前初始化一些bean。該方法的內部又調用了
preInstantiateSingletons
方法
SCOPE_PROTOTYPE
類型的類,非單例,不會被提前初始化bean,所以程序能夠正常啓動。
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
}
Requested bean is currently in creation: Is there an unresolvable circular reference?
構造器注入
@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?
單例的代理對象setter注入
@Async
註解的場景,會通過
AOP
自動生成代理對象。
@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循環依賴
@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
註解是沒問題的,反而加了這個註解會出現循環依賴問題。
AbstractBeanFactory
類的
doGetBean
方法的這段代碼中:
4.出現循環依賴如何解決?
生成代理對象產生的循環依賴
-
使用 @Lazy
註解,延遲加載 -
使用 @DependsOn
註解,指定加載先後關係 -
修改文件名稱,改變循環依賴類的加載順序
使用@DependsOn產生的循環依賴
@DependsOn
註解循環依賴的地方,迫使它不循環依賴就可以解決問題。
多例循環依賴
構造器循環依賴
@Lazy
註解解決。
有道無術,術可成;有術無道,止於術
歡迎大家關注Java之道公衆號
好文章,我在看❤️
本文分享自微信公衆號 - Hollis(hollischuang)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。