1. 什么是Spring循環(huán)依賴?
? Spring循環(huán)依賴指的是兩個或多個Bean之間相互依賴,形成一個環(huán)狀依賴的情況。通俗的說,就是A依賴B,B依賴C,C依賴A,這樣就形成了一個循環(huán)依賴的環(huán)。
? Spring循環(huán)依賴通常會導(dǎo)致Bean無法正確地被實例化,從而導(dǎo)致應(yīng)用程序無法正常啟動或者出現(xiàn)異常。因此,Spring循環(huán)依賴是一種需要盡量避免的情況。
2. 常見形成原因
1. 構(gòu)造函數(shù)循環(huán)依賴
? 在使用構(gòu)造函數(shù)注入Bean時,如果兩個Bean之間相互依賴,就可能會形成構(gòu)造函數(shù)循環(huán)依賴,例如:
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}}
==============
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
}}
上述代碼,A、B的構(gòu)造函數(shù)分別需要創(chuàng)建對方,A依賴B,B依賴A,它們之間形成了一個循環(huán)依賴。
當(dāng)Spring容器啟動時,它會嘗試先實例化A,但是在實例化A的時候需要先實例化B,而實例化B的時候需要先實例化A,這樣就形成了一個循環(huán)依賴的死循環(huán),從而導(dǎo)致應(yīng)用程序無法正常啟動。
2. 屬性循環(huán)依賴
? 在使用屬性注入Bean時,如果兩個Bean之間相互依賴,就可能會形成屬性循環(huán)依賴。例如:
@Component
public class A {
@Autowired
private B b;
}
=============
@Component
public class B {
@Autowired
private A a;
}
類似的,同樣Spring在實例化A時會注入B,而注入B時又需要注入A,形成循環(huán)依賴
3. Spring如何解決循環(huán)依賴
要了解如何解決Spring的循環(huán)依賴,就必須要從Spring對Bean的創(chuàng)建過程入手,從原理上去理解,而不是單純記憶
3.1 Spring 是如何創(chuàng)建Bean的?
? (1) 首先Spring容器啟動之后,會根據(jù)使用不同類型的Application Context,通過不同的方式去加載Bean配置,如xml方式、注解方式,將這些Bean配置加載到容器中,作為Bean定義包裝成BeanDefinition對象保存起來,為下一步創(chuàng)建Bean做準(zhǔn)備。
? (2) 根據(jù)加載的Bean定義信息,通過反射來創(chuàng)建Bean實例,如果是普通Bean,則直接創(chuàng)建Bean,如果是FactoryBean,說明真正要創(chuàng)建的對象為getObject()的返回值,調(diào)用getObject()將返回值作為Bean。
? (3) 目前已經(jīng)完成了Bean實例的創(chuàng)建,還需要對依賴的屬性進(jìn)行裝配,例如,平時開發(fā)中Controller中,往往需要將Service Bean注入進(jìn)來,循環(huán)依賴也是在這一步解決的,后面會詳細(xì)說明,如果通過@Autowired注解注入的成員變量,則會通過AutowiredAnnotationBeanPostProcessor后置處理器進(jìn)行注入,如果xml自動注入,則會根據(jù)按名字自動裝配和按類型自動裝配分別進(jìn)行處理。
? (4) 自動裝配完成后,將完整的Bean對象保存到Spring 緩存中,接下來進(jìn)入Bean的初始化流程,執(zhí)行Bean的后置處理器的前置處理方法,如果Bean本身實現(xiàn)了InitializingBean接口,就去執(zhí)行對應(yīng)的afterPropertiesSet()方法,最后再執(zhí)行Bean的后置處理器的后置處理方法。
3.2 Spring三級緩沖機(jī)制
1. Spring可以解決的依賴循環(huán)
回到上述的依賴循環(huán)案例,A、B相互依賴時,產(chǎn)生的循環(huán)依賴現(xiàn)象還需進(jìn)一步細(xì)分:
注意:只有單例的 Bean 存在循環(huán)依賴的情況,Spring才可以解決,原型(Prototype)情況下,Spring 會直接拋出異常。
? Spring 不支持基于構(gòu)造器注入的循環(huán)依賴。 但是假如 AB 循環(huán)依賴,如果一個是構(gòu)造器注入,一個是 setter 注入呢?
看看幾種情形:
第四種可以而第五種不可以的原因是 Spring 在創(chuàng)建 Bean 時默認(rèn)會根據(jù)自然排序進(jìn)行創(chuàng)建,所以 A 會先于 B 進(jìn)行創(chuàng)建。
簡單總結(jié),當(dāng)循環(huán)依賴的實例都采用 setter 方法注入的時候,Spring 可以支持,都采用構(gòu)造器注入的時候,不支持,構(gòu)造器注入和 setter 注入同時存在的時候,看天。
2. 三層緩存機(jī)制
bean的創(chuàng)建流程:
? 依賴注入就發(fā)生在第二步,屬性賦值,結(jié)合這個過程,Spring 通過三級緩存解決了循環(huán)依賴:
- 一級緩存 :
Map<String,Object>
singletonObjects,單例池,用于保存實例化、屬性賦值(注入)、初始化完成的 bean 實例 - 二級緩存 :
Map<String,Object>
earlySingletonObjects,早期曝光對象,用于保存實例化完成的 bean 實例 - 三級緩存 :
Map<String,ObjectFactory<?>>
singletonFactories,早期曝光對象工廠,用于保存 bean 創(chuàng)建工廠,以便于后面擴(kuò)展有機(jī)會創(chuàng)建代理對象。
我們來看一下三級緩存解決循環(huán)依賴的過程:
當(dāng) A、B 兩個類發(fā)生循環(huán)依賴時:
A 實例的初始化過程:
- 創(chuàng)建 A 實例,實例化的時候把 A 對象??放?三級緩存,表示 A 開始實例化了,雖然我這個對象還不完整,但是先曝光出來讓大家知道
1
- A 注?屬性時,發(fā)現(xiàn)依賴 B,此時 B 還沒有被創(chuàng)建出來,所以去實例化 B
- 同樣,B 注?屬性時發(fā)現(xiàn)依賴 A,它就會從緩存里找 A 對象。依次從?級到三級緩存查詢 A,從三級緩存通過對象??拿到 A,發(fā)現(xiàn) A 雖然不太完善,但是存在,把 A 放??級緩存,同時刪除三級緩存中的 A,此時,B 已經(jīng)實例化并且初始化完成,把 B 放入?級緩存。
2
- 接著 A 繼續(xù)屬性賦值,順利從?級緩存拿到實例化且初始化完成的 B 對象,A 對象創(chuàng)建也完成,刪除?級緩存中的 A,同時把 A 放??級緩存
- 最后,?級緩存中保存著實例化、初始化都完成的 A、B 對象
5
所以,我們就知道為什么 Spring 能解決 setter 注入的循環(huán)依賴了,因為實例化和屬性賦值是分開的,所以里面有操作的空間。如果都是構(gòu)造器注入的化,那么都得在實例化這一步完成注入,所以自然是無法支持了。文章來源地址http://www.zghlxwxcb.cn/news/detail-857049.html文章來源:http://www.zghlxwxcb.cn/news/detail-857049.html
所以,我們就知道為什么 Spring 能解決 setter 注入的循環(huán)依賴了,因為實例化和屬性賦值是分開的,所以里面有操作的空間。如果都是構(gòu)造器注入的化,那么都得在實例化這一步完成注入,所以自然是無法支持了。
到了這里,關(guān)于【Spring】Spring的循環(huán)依賴以及解決方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!