Spring為何要用三級(jí)緩存來(lái)解決循環(huán)依賴問(wèn)題
我們都知道Spring為了解決循環(huán)依賴使用了三級(jí)緩存
Spring三級(jí)緩存
一級(jí)緩存singletonObjects
用于保存BeanName和創(chuàng)建bean實(shí)例之間的關(guān)系,beanName -> bean instance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
二級(jí)緩存earlySingletonObjects
保存提前曝光的單例bean對(duì)象
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
三級(jí)緩存singletonFactories
保存beanName和創(chuàng)建bean實(shí)例之間的關(guān)系,與singletonObjects不同的地方在于,當(dāng)一個(gè)單例bean被放到這里面后,bean在創(chuàng)建過(guò)程中,可以通過(guò)getBean方法獲取到,目的是用來(lái)檢測(cè)循環(huán)引用
private final Map<String, Object> singletonFactories = new HashMap(16);
在創(chuàng)建bean的時(shí)候,首先從緩存中獲取單例的bean,這個(gè)緩存就是singletonObjects,如果獲取不到且bean正在創(chuàng)建中,就再?gòu)膃arlySingletonObjects中獲取,如果還是獲取不到且允許從singletonFactories中通過(guò)getObject拿到對(duì)象,就從singletonFactories中獲取,如果獲取到了就存入earlySingletonObjects并從singletonFactories中移除。
為什么要使用三級(jí)緩存?
一級(jí)緩存
首先我們看看一級(jí)緩存行不行,如果只留第一級(jí)緩存,那么單例的Bean都存在singletonObjects 中,Spring循環(huán)依賴主要基于Java引用傳遞,當(dāng)獲取到對(duì)象時(shí),對(duì)象的field或者屬性可以延后設(shè)置,理論上可以,但是如果延后設(shè)置出了問(wèn)題,就會(huì)導(dǎo)致完整的Bean和不完整的Bean都在一級(jí)緩存中,這個(gè)引用時(shí)就有空指針的可能,所以一級(jí)緩存不行,至少要有singletonObjects 和earlySingletonObjects 兩級(jí)。
兩級(jí)緩存
那么我們?cè)倏纯磧杉?jí)緩存行不行
現(xiàn)在有A的field或者setter依賴B的實(shí)例對(duì)象,同時(shí)B的field或者setter依賴了A的實(shí)例,A首先開始創(chuàng)建,并將自己暴露到 earlySingletonObjects 中,開始填充屬性,此時(shí)發(fā)現(xiàn)自己依賴B的屬性,嘗試去get(B),發(fā)現(xiàn)B還沒(méi)有被創(chuàng)建,所以開始創(chuàng)建B,在進(jìn)行屬性填充時(shí)初始化A,就從earlySingletonObjects 中獲取到了實(shí)例化但沒(méi)有任何屬性的A,B拿到A后完成了初始化階段,將自己放到singletonObjects中,此時(shí)返回A,A拿到B的對(duì)象繼續(xù)完成初始化,完成后將自己放到singletonObjects中,由A與B中所表示的A的屬性地址是一樣的,所以A的屬性填充完后,B也獲取了A的屬性,這樣就解決了循環(huán)的問(wèn)題。
似乎完美解決,如果就這么使用的話也沒(méi)什么問(wèn)題,但是再加上AOP情況就不同了,被AOP增強(qiáng)的Bean會(huì)在初始化后代理成為一個(gè)新的對(duì)象,也就是說(shuō):
如果有AOP,A依賴于B,B依賴于A,A實(shí)例化完成暴露出去,開始注入屬性,發(fā)現(xiàn)引用B,B開始實(shí)例化,使用A暴露的對(duì)象,初始化完成后封裝成代理對(duì)象,A再將代理后的B注入,再做代理,那么代理A中的B就是代理后的B,但是代理后的B中的A是沒(méi)用代理的A。
顯然這是不對(duì)的,所以在Spring中存在第三級(jí)緩存,在創(chuàng)建對(duì)象時(shí)判斷是否是單例,允許循環(huán)依賴,正在創(chuàng)建中,就將其從earlySingletonObjects中移除掉,并在singletonFactories放入新的對(duì)象,這樣后續(xù)再查詢beanName時(shí)會(huì)走到singletonFactory.getObject(),其中就會(huì)去調(diào)用各個(gè)beanPostProcessor的getEarlyBeanReference方法,返回的對(duì)象就是代理后的對(duì)象。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
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);
}
}
}
總結(jié)
到此這篇關(guān)于Spring為何要用三級(jí)緩存來(lái)解決循環(huán)依賴問(wèn)題的文章就介紹到這了,更多相關(guān)Spring用三級(jí)緩存解決循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA重復(fù)調(diào)用接口導(dǎo)致數(shù)據(jù)不一致的問(wèn)題解決
在使用JAVA進(jìn)行開發(fā)時(shí),我們經(jīng)常會(huì)遇到要調(diào)用接口來(lái)獲取數(shù)據(jù)的情況,本文主要介紹了JAVA重復(fù)調(diào)用接口導(dǎo)致數(shù)據(jù)不一致的問(wèn)題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
java實(shí)現(xiàn)斗地主發(fā)牌系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)斗地主發(fā)牌系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
SpringBoot+MinIO實(shí)現(xiàn)文件切片極速詳解
在現(xiàn)代Web應(yīng)用中,文件上傳是一個(gè)常見的需求,尤其是對(duì)于大文件的上傳,如視頻、音頻或大型文檔,所以本文就來(lái)為大家介紹一下如何使用Spring Boot和MinIO實(shí)現(xiàn)文件切片極速上傳技術(shù)吧2023-12-12
淺談java中為什么重寫equals后需要重寫hashCode
今天帶各位學(xué)習(xí)一下java中為什么重寫equals后需要重寫hashCode,文中有非常詳細(xì)的圖文介紹及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05
Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn)
最近在對(duì)一個(gè)集合列表的數(shù)據(jù)進(jìn)行排序,需求是要集合數(shù)據(jù)按照一個(gè)排序狀態(tài)值進(jìn)行排序,而這個(gè)狀態(tài)值,不是按照從小到大這樣的順序排序的,而是要按照特定的順序,所以本文給大家介紹了Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn),需要的朋友可以參考下2024-07-07
詳解五種方式讓你在java中讀取properties文件內(nèi)容不再是難題
這篇文章主要介紹了詳解五種方式讓你在java中讀取properties文件內(nèi)容不再是難題 ,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12

