今天就跟大家聊聊有關(guān)Spring中怎么解決循環(huán)依賴(lài),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
深圳網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來(lái)到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專(zhuān)注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
循環(huán)依賴(lài),其實(shí)就是循環(huán)引用,就是兩個(gè)或者兩個(gè)以上的 bean 互相引用對(duì)方,最終形成一個(gè)閉環(huán),如 A 依賴(lài) B,B 依賴(lài) C,C 依賴(lài) A。如下圖所示:
Spring中的循環(huán)依賴(lài),其實(shí)就是一個(gè)死循環(huán)的過(guò)程,在初始化 A 的時(shí)候發(fā)現(xiàn)依賴(lài)了 B,這時(shí)就會(huì)去初始化 B,然后又發(fā)現(xiàn) B 依賴(lài) C,跑去初始化 C,初始化 C 的時(shí)候發(fā)現(xiàn)依賴(lài)了 A,則又會(huì)去初始化 A,依次循環(huán)永不退出,除非有終結(jié)條件。
一般來(lái)說(shuō),Spring 循環(huán)依賴(lài)的情況有兩種:
構(gòu)造器的循環(huán)依賴(lài)。 field 屬性的循環(huán)依賴(lài)。 對(duì)于構(gòu)造器的循環(huán)依賴(lài),Spring 是無(wú)法解決的,只能拋出 BeanCurrentlyInCreationException
異常表示循環(huán)依賴(lài),所以下面我們分析的都是基于 field 屬性的循環(huán)依賴(lài)。
在前文 Spring Ioc源碼分析 之 Bean的加載(三):各個(gè) scope 的 Bean 創(chuàng)建 中提到,Spring 只解決 scope 為 singleton 的循環(huán)依賴(lài)。對(duì)于scope 為 prototype 的 bean ,Spring 無(wú)法解決,直接拋出 BeanCurrentlyInCreationException 異常。
為什么 Spring 不處理 prototype bean 呢?其實(shí)如果理解 Spring 是如何解決 singleton bean 的循環(huán)依賴(lài)就明白了。這里先留個(gè)疑問(wèn),我們先來(lái)看下 Spring 是如何解決 singleton bean 的循環(huán)依賴(lài)的。
在AbstractBeanFactory 的 doGetBean()
方法中,我們根據(jù)BeanName去獲取Singleton Bean的時(shí)候,會(huì)先從緩存獲取。
代碼如下:
//DefaultSingletonBeanRegistry.java @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從一級(jí)緩存緩存 singletonObjects 中加載 bean Object singletonObject = this.singletonObjects.get(beanName); // 緩存中的 bean 為空,且當(dāng)前 bean 正在創(chuàng)建 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 加鎖 synchronized (this.singletonObjects) { // 從 二級(jí)緩存 earlySingletonObjects 中獲取 singletonObject = this.earlySingletonObjects.get(beanName); // earlySingletonObjects 中沒(méi)有,且允許提前創(chuàng)建 if (singletonObject == null && allowEarlyReference) { // 從 三級(jí)緩存 singletonFactories 中獲取對(duì)應(yīng)的 ObjectFactory ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //從單例工廠中獲取bean singletonObject = singletonFactory.getObject(); // 添加到二級(jí)緩存 this.earlySingletonObjects.put(beanName, singletonObject); // 從三級(jí)緩存中刪除 this.singletonFactories.remove(beanName); } } } } return singletonObject; }
這段代碼涉及的3個(gè)關(guān)鍵的變量,分別是3個(gè)級(jí)別的緩存,定義如下:
/** Cache of singleton objects: bean name --> bean instance */ //單例bean的緩存 一級(jí)緩存 private final MapsingletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ //單例對(duì)象工廠緩存 三級(jí)緩存 private final Map > singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name --> bean instance */ //預(yù)加載單例bean緩存 二級(jí)緩存 //存放的 bean 不一定是完整的 private final Map earlySingletonObjects = new HashMap<>(16);
getSingleton()
的邏輯比較清晰:
首先,嘗試從一級(jí)緩存singletonObjects
中獲取單例Bean。
如果獲取不到,則從二級(jí)緩存earlySingletonObjects
中獲取單例Bean。
如果仍然獲取不到,則從三級(jí)緩存singletonFactories
中獲取單例BeanFactory。
最后,如果從三級(jí)緩存中拿到了BeanFactory,則通過(guò)getObject()
把Bean存入二級(jí)緩存中,并把該Bean的三級(jí)緩存刪掉。
看到這里可能會(huì)有些疑問(wèn),這3個(gè)緩存怎么就解決了singleton循環(huán)依賴(lài)了呢?
先別著急,我們現(xiàn)在分析了獲取緩存的代碼,再來(lái)看下存儲(chǔ)緩存的代碼。 在 AbstractAutowireCapableBeanFactory的 doCreateBean()
方法中,有這么一段代碼:
// AbstractAutowireCapableBeanFactory.java boolean earlySingletonExposure = (mbd.isSingleton() // 單例模式 && this.allowCircularReferences // 允許循環(huán)依賴(lài) && isSingletonCurrentlyInCreation(beanName)); // 當(dāng)前單例 bean 是否正在被創(chuàng)建 if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 為了后期避免循環(huán)依賴(lài),提前將創(chuàng)建的 bean 實(shí)例加入到三級(jí)緩存 singletonFactories 中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
這段代碼就是put三級(jí)緩存singletonFactories
的地方,其核心邏輯是,當(dāng)滿足以下3個(gè)條件時(shí),把bean加入三級(jí)緩存中:
單例
允許循環(huán)依賴(lài)
當(dāng)前單例Bean正在創(chuàng)建
addSingletonFactory(String beanName, ObjectFactory> singletonFactory)
方法,代碼如下:
// DefaultSingletonBeanRegistry.java protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
從這段代碼我們可以看出,singletonFactories 這個(gè)三級(jí)緩存才是解決 Spring Bean 循環(huán)依賴(lài)的關(guān)鍵。同時(shí)這段代碼發(fā)生在 createBeanInstance(...)
方法之后,也就是說(shuō)這個(gè) bean 其實(shí)已經(jīng)被創(chuàng)建出來(lái)了,但是它還沒(méi)有完善(沒(méi)有進(jìn)行屬性填充和初始化),但是對(duì)于其他依賴(lài)它的對(duì)象而言已經(jīng)足夠了(已經(jīng)有內(nèi)存地址了,可以根據(jù)對(duì)象引用定位到堆中對(duì)象),能夠被認(rèn)出來(lái)了。
到這里我們發(fā)現(xiàn)三級(jí)緩存 singletonFactories 和 二級(jí)緩存 earlySingletonObjects 中的值都有出處了,那一級(jí)緩存在哪里設(shè)置的呢?在類(lèi) DefaultSingletonBeanRegistry 中,可以發(fā)現(xiàn)這個(gè) addSingleton(String beanName, Object singletonObject)
方法,代碼如下:
// DefaultSingletonBeanRegistry.java protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { //添加至一級(jí)緩存,同時(shí)從二級(jí)、三級(jí)緩存中刪除。 this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
該方法是在 #doGetBean(...) 方法中,處理不同 scope 時(shí),如果是 singleton調(diào)用的,如下圖所示:
也就是說(shuō),一級(jí)緩存里面是完整的Bean。
小結(jié):
一級(jí)緩存里面是完整的Bean,是當(dāng)一個(gè)Bean完全創(chuàng)建后才put
三級(jí)緩存是不完整的BeanFactory,是當(dāng)一個(gè)Bean在new之后就put(沒(méi)有屬性填充、初始化)
二級(jí)緩存是對(duì)三級(jí)緩存的易用性處理,只不過(guò)是通過(guò)getObject()
方法從三級(jí)緩存的BeanFactory中取出Bean
總結(jié)
現(xiàn)在我們?cè)賮?lái)回顧下Spring解決單例循環(huán)依賴(lài)的方案:
Spring 在創(chuàng)建 bean 的時(shí)候并不是等它完全完成,而是在創(chuàng)建過(guò)程中將創(chuàng)建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三級(jí)緩存中)。
這樣,一旦下一個(gè) bean 創(chuàng)建的時(shí)候需要依賴(lài) bean ,則從三級(jí)緩存中獲取。
舉個(gè)栗子:
比如我們團(tuán)隊(duì)里要報(bào)名參加活動(dòng),你不用上來(lái)就把你的生日、性別、家庭信息什么的全部填完,你只要先報(bào)個(gè)名字,統(tǒng)計(jì)下人數(shù)就行,之后再慢慢完善你的個(gè)人信息。
核心思想:提前暴露,先用著
最后來(lái)描述下就上面那個(gè)循環(huán)依賴(lài) Spring 解決的過(guò)程:
首先 A 完成初始化第一步并將自己提前曝光出來(lái)(通過(guò) 三級(jí)緩存 將自己提前曝光),在初始化的時(shí)候,發(fā)現(xiàn)自己依賴(lài)對(duì)象 B,此時(shí)就會(huì)去嘗試 get(B),這個(gè)時(shí)候發(fā)現(xiàn) B 還沒(méi)有被創(chuàng)建出來(lái)
然后 B 就走創(chuàng)建流程,在 B 初始化的時(shí)候,同樣發(fā)現(xiàn)自己依賴(lài) C,C 也沒(méi)有被創(chuàng)建出來(lái)
這個(gè)時(shí)候 C 又開(kāi)始初始化進(jìn)程,但是在初始化的過(guò)程中發(fā)現(xiàn)自己依賴(lài) A,于是嘗試 get(A),這個(gè)時(shí)候由于 A 已經(jīng)添加至緩存中(三級(jí)緩存 singletonFactories ),通過(guò) ObjectFactory 提前曝光,所以可以通過(guò) ObjectFactory#getObject()
方法來(lái)拿到 A 對(duì)象,C 拿到 A 對(duì)象后順利完成初始化,然后將自己添加到一級(jí)緩存中
回到 B ,B 也可以拿到 C 對(duì)象,完成初始化,A 可以順利拿到 B 完成初始化。到這里整個(gè)鏈路就已經(jīng)完成了初始化過(guò)程了
http://cmsblogs.com/?p=todo (小明哥)
看完上述內(nèi)容,你們對(duì)Spring中怎么解決循環(huán)依賴(lài)有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。