十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊
量身定制 + 運營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
前言
在面試的時候這兩年有一個非常高頻的關(guān)于spring的問題,那就是spring是如何解決循環(huán)依賴的。這個問題聽著就是輕描淡寫的一句話,其實考察的內(nèi)容還是非常多的,主要還是考察的應(yīng)聘者有沒有研究過spring的源碼。但是說實話,spring的源碼其實非常復(fù)雜的,研究起來并不是個簡單的事情,所以我們此篇文章只是為了解釋清楚Spring是如何解決循環(huán)依賴的這個問題。
什么樣的依賴算是循環(huán)依賴?
用過Spring框架的人都對依賴注入這個詞不陌生,一個Java類A中存在一個屬性是類B的一個對象,那么我們就說類A的對象依賴類B,而在Spring中是依靠的IOC來實現(xiàn)的對象注入,也就是說創(chuàng)建對象的過程是IOC容器來實現(xiàn)的,并不需要自己在使用的時候通過new關(guān)鍵字來創(chuàng)建對象。
那么當(dāng)類A中依賴類B的對象,而類B中又依賴類C的對象,最后類C中又依賴類A的對象的時候,這種情況最終的依賴關(guān)系會形成一個環(huán),這就是循環(huán)依賴。
循環(huán)依賴的類型
根據(jù)注入的時機(jī)可以分為兩種:
構(gòu)造器循環(huán)依賴
依賴的對象是通過構(gòu)造方法傳入的,在實例化bean的時候發(fā)生。
賦值屬性循環(huán)依賴
依賴的對象是通過setter方法傳入的,對象已經(jīng)實例化,在屬性賦值和依賴注入的時候發(fā)生。
構(gòu)造器循環(huán)依賴,本質(zhì)上是無解的,實例化A的時候調(diào)用A的構(gòu)造器,發(fā)現(xiàn)依賴了B,又去實例化B,然后調(diào)用B的構(gòu)造器,發(fā)現(xiàn)又依賴的C,然后調(diào)用C的構(gòu)造器去實例化,結(jié)果發(fā)起C的構(gòu)造器里依賴了A,這就是個死循環(huán)無解。所以Spring也是不支持構(gòu)造器循環(huán)依賴的,當(dāng)發(fā)現(xiàn)存在構(gòu)造器循環(huán)依賴時,會直接拋出BeanCurrentlyInCreationException
異常。
賦值屬性循環(huán)依賴,Spring只支持bean在單例模式下的循環(huán)依賴,其他模式下的循環(huán)依賴Spring也是會拋出BeanCurrentlyInCreationException
異常的。Spring通過對還在創(chuàng)建過程中的單例bean,進(jìn)行緩存并提前暴露該單例,使得其他實例可以提前引用到該單例bean。
Spring為什么只支持單例模式下的bean的賦值情況下的循環(huán)依賴
在prototype的模式下的bean,使用了一個ThreadLocal變量prototypesCurrentlyInCreation
來記錄當(dāng)前線程正在創(chuàng)建中的bean,這個變量在AbtractBeanFactory
類里。在創(chuàng)建前用beanName記錄bean,在創(chuàng)建完成后刪除bean。在prototypesCurrentlyInCreation
里采用了一個Set對象來存儲正在創(chuàng)建中的bean。我們都知道Set是不允許存在重復(fù)對象的,這樣就能保證同一個bean在一個線程中只能有一個正在創(chuàng)建。
下面是prototypesCurrentlyInCreation
變量在刪除bean時的操作,在AbtractBeanFactory
的beforePrototypeCreation
操作里。
protected void afterPrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal instanceof String) { this.prototypesCurrentlyInCreation.remove(); } else if (curVal instanceof Set) { SetbeanNameSet = (Set ) curVal; beanNameSet.remove(beanName); if (beanNameSet.isEmpty()) { this.prototypesCurrentlyInCreation.remove(); } } }