Spring通過(guò)三級(jí)緩存解決循環(huán)依賴(lài)問(wèn)題的過(guò)程詳解
1. 三級(jí)緩存解決的問(wèn)題場(chǎng)景
循環(huán)依賴(lài)指的是在對(duì)象之間存在相互依賴(lài)關(guān)系,形成一個(gè)閉環(huán),導(dǎo)致無(wú)法準(zhǔn)確地完成對(duì)象的創(chuàng)建和初始化;當(dāng)兩個(gè)或多個(gè)對(duì)象彼此之間相互引用,而這種相互引用形成一個(gè)循環(huán)時(shí),就可能出現(xiàn)循環(huán)依賴(lài)問(wèn)題。
在早期的 Spring 版本中是可以自動(dòng)解決的循環(huán)依賴(lài)的問(wèn)題的,
public class A { @Autowired private B b; } public class B { @Autowired private A a; }
但要注意,Spring 解決循環(huán)依賴(lài)是有前提條件的,
第一,要求互相依賴(lài)的 Bean 必須要是單例的 Bean。
這是因?yàn)閷?duì)于原型范圍的 Bean(prototype scope),每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 Bean 實(shí)例,這樣每次嘗試解析循環(huán)依賴(lài)時(shí),都會(huì)產(chǎn)生新的 Bean 實(shí)例,導(dǎo)致無(wú)限循環(huán),由于沒(méi)有全局的、持續(xù)的單例實(shí)例的緩存來(lái)引用,因此循環(huán)依賴(lài)無(wú)法得到解決。
第二,依賴(lài)注入的方式不能都是構(gòu)造函數(shù)注入的方式。
當(dāng)使用構(gòu)造函數(shù)注入時(shí),一個(gè) Bean 的實(shí)例在構(gòu)造函數(shù)被完全調(diào)用之前是不會(huì)被創(chuàng)建的;如果 Bean A 的構(gòu)造函數(shù)依賴(lài)于 Bean B,而 Bean B 的構(gòu)造函數(shù)又依賴(lài)于 Bean A,那么就會(huì)產(chǎn)生一個(gè)死鎖的情況,因?yàn)閮烧叨疾荒茉趯?duì)方初始化之前完成初始化。
public class C { private D d; @Autowired public C(D d) { this.dService = dService; } } public class D { private C c; @Autowired public D(C c) { this.c = c; } }
Spring 源碼中關(guān)于三級(jí)緩存的定義如下:
// 一級(jí)緩存 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級(jí)緩存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級(jí)緩存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
所以說(shuō),所謂的“三級(jí)緩存”就是是指三個(gè) Map 數(shù)據(jù)結(jié)構(gòu),分別用于存儲(chǔ)不同狀態(tài)的 Bean。
2. 三級(jí)緩存的差異性
一級(jí)緩存:
一級(jí)緩存保存的是已經(jīng)完全初始化和實(shí)例化的 Bean 對(duì)象,在程序中使用的 Bean 通常就是從這個(gè)緩存中獲取的;這個(gè)緩存的目的是確保 Bean 只初始化一次(是單例的),避免多次實(shí)例化相同的Bean對(duì)象,提高性能。
二級(jí)緩存:
二級(jí)緩存用來(lái)解決 Bean 創(chuàng)建過(guò)程中的循環(huán)依賴(lài)問(wèn)題,它存儲(chǔ)的是尚未完成屬性注入和初始化的“半成品”Bean 對(duì)象;當(dāng) Spring容器發(fā)現(xiàn)兩個(gè)或多個(gè) Bean 之間存在循環(huán)依賴(lài)時(shí),也就是當(dāng)一個(gè) Bean 創(chuàng)建過(guò)程中需要引用另一個(gè)正在創(chuàng)建的 Bean,Spring 將創(chuàng)建需要的這些未完全初始化的對(duì)象提前暴露在二級(jí)緩存中,以便其他 Bean 進(jìn)行引用,確保它們之間的依賴(lài)能夠被滿(mǎn)足。
三級(jí)緩存:
三級(jí)緩存中存儲(chǔ)的是 ObjectFactory<?> 類(lèi)型的代理工廠(chǎng)對(duì)象,主要用于處理存在 AOP 時(shí)的循環(huán)依賴(lài)問(wèn)題;每個(gè) Bean 都對(duì)應(yīng)一個(gè) ObjectFactory 對(duì)象,通過(guò)調(diào)用該對(duì)象的 getObject 方法,可以獲取到早期暴露出去的 Bean;在該 Bean 要被其他 Bean 引用時(shí),Spring 就會(huì)用工廠(chǎng)對(duì)象創(chuàng)建出該 Bean 的實(shí)例對(duì)象,最終當(dāng)該 Bean 完成構(gòu)造的所有步驟后就會(huì)將該 Bean 放入到一級(jí)緩存中。
3. 循環(huán)依賴(lài)時(shí)的處理流程
當(dāng) Spring 發(fā)生循環(huán)依賴(lài)時(shí)(以最開(kāi)始介紹的場(chǎng)景為例,A B 兩個(gè) Bean 相互依賴(lài)),以下是完善的執(zhí)行流程:
- 遍歷待創(chuàng)建的所有 beanName:在容器啟動(dòng)時(shí),Spring 會(huì)遍歷所有需要?jiǎng)?chuàng)建的 Bean 名稱(chēng),在第一次遍歷到 A 時(shí),就開(kāi)始獲取 Bean A;如果 Bean A 的實(shí)例不在一級(jí)、二級(jí)緩存中(緩存中沒(méi)有值),Spring 會(huì)開(kāi)始正常的 Bean 創(chuàng)建流程。
- Bean A 的創(chuàng)建:Bean A 的創(chuàng)建過(guò)程開(kāi)始,然后 Spring 會(huì)檢查是否 A 是單例(Singleton),同時(shí) A 是否已經(jīng)創(chuàng)建完成;如果 A 是單例且尚未創(chuàng)建完成,將 A 的 BeanFactory 存入三級(jí)緩存。
- 處理依賴(lài)注入:A 開(kāi)始處理 @Autowired 注解,嘗試注入 B 屬性;Spring 會(huì)在一級(jí)、二級(jí)緩存中來(lái)查找 Bean B,如果找不到,則開(kāi)始正常創(chuàng)建 Bean B 的流程。
- Bean B 的創(chuàng)建:Bean B 的創(chuàng)建過(guò)程類(lèi)似于 A,首先判斷B是否是單例,且是否已創(chuàng)建完成,如果否,B 的BeanFactory 也會(huì)被存入三級(jí)緩存。
- B 注入 A 屬性:B 開(kāi)始注入 A 屬性,嘗試從一級(jí)、二級(jí)緩存中查找 A;如果在緩存中找不到 A,B 會(huì)嘗試從三級(jí)緩存獲取 A 的 BeanFactory,并通過(guò) BeanFactory的getObject()方法獲取 A 的屬性,此時(shí),A 被存入二級(jí)緩存,同時(shí)清除三級(jí)緩存;因此,B 能夠成功注入 A 屬性,B 接著執(zhí)行初始化,處于實(shí)例化和初始化都已完成的完全狀態(tài)。
- B 存入一級(jí)緩存:B執(zhí)行addSingleton(),將完全狀態(tài)的 B 存入一級(jí)緩存,并清空二級(jí),三級(jí)緩存。
- A 繼續(xù)注入 B 屬性:A 繼續(xù)注入 B 屬性,調(diào)用beanFactory.getBean()方法獲取 B,由于第六步已經(jīng)將 B 存入一級(jí)緩存,A 可以直接獲取 B,成功注入 B 屬性, A 接著執(zhí)行初始化,得到一個(gè)完全狀態(tài)的 A。
- A 存入一級(jí)緩存:A 執(zhí)行addSingleton(),將完全狀態(tài)的 A 存入一級(jí)緩存,并清空二級(jí)緩存。
此時(shí),A 和 B 都被實(shí)例化和初始化完成,解決了循環(huán)依賴(lài)的問(wèn)題;這個(gè)流程確保了每個(gè)Bean在需要時(shí)都能夠獲取到已完全初始化的依賴(lài)項(xiàng)。
常見(jiàn)疑問(wèn)解答
問(wèn)題一:為什么在 Bean B 被注入 Bean A 之前,需要將 Bean A 存入二級(jí)緩存?
主要原因是,如果存在其他循環(huán)依賴(lài)需要用到 A,從二級(jí)緩存中直接取出早期的 Bean A 對(duì)象會(huì)更加高效。
問(wèn)題二:為什么創(chuàng)建完 Bean 后要清空二、三級(jí)緩存?
清空是為了節(jié)省存儲(chǔ)空間,一旦 Bean 完全初始化并存儲(chǔ)在一級(jí)緩存中,其在二、三級(jí)緩存中的記錄就不再需要了。
問(wèn)題三:三級(jí)緩存為什么不能解決構(gòu)造器引起的循環(huán)依賴(lài)?
這是因?yàn)闃?gòu)造器引起的循環(huán)依賴(lài)發(fā)生在 Bean 的實(shí)例化階段,這個(gè)階段比二、三級(jí)緩存處理的階段還要早,無(wú)法創(chuàng)建出早期的半成品對(duì)象。
問(wèn)題四:如果不使用三級(jí)緩存,只使用二級(jí)緩存,能否解決循環(huán)依賴(lài)?
肯定是不能的,二級(jí)緩存存儲(chǔ)的 Bean 可能是兩種類(lèi)型,一種是實(shí)例化階段創(chuàng)建出來(lái)的對(duì)象,另一種是實(shí)例化階段創(chuàng)建出來(lái)的對(duì)象的代理對(duì)象;是否需要代理對(duì)象取決于你的配置需要,如是否添加了事務(wù)注解或自定義 AOP 切面;如果放棄使用三級(jí)緩存,即沒(méi)有 ObjectFactory,那么就需要將早期的 Bean 放入二級(jí)緩存;但問(wèn)題是,應(yīng)該將未被代理的 Bean 還是代理的 Bean 放入二級(jí)緩存,這只能在屬性注入階段,處理注解時(shí)才能分辨。
- 如果直接往二級(jí)緩存添加沒(méi)有被代理的 Bean,那么此時(shí)注入給其它對(duì)象的 Bean 可能跟最后完全生成的 Bean 是不一樣的,因?yàn)樽詈笊墒褂玫氖强赡艽韺?duì)象,此時(shí)注入的是原始對(duì)象,這這種情況是不允許發(fā)生的。
- 如果直接往二級(jí)緩存添加一個(gè)代理 Bean,在不確定是否要使用代理對(duì)象的情況下,就有提前暴露代理對(duì)象的可能;正常的代理的對(duì)象都是初始化后期調(diào)用生成的,是基于后置處理器的,若提早的代理就違背了 Bean 定義的生命周期。
Spring 在一個(gè)三級(jí)緩存放置一個(gè)工廠(chǎng),如果產(chǎn)生循環(huán)依賴(lài) ,這個(gè)工廠(chǎng)的作用就是判斷這個(gè)對(duì)象是否需要代理,如果否則直接返回,如果是則返回代理對(duì)像。
4. 源碼驗(yàn)證
在項(xiàng)目中雙擊 Shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到 550 行左右的 doCreateBean 方法,重點(diǎn)看一下 580 行到 600 行這20行代碼就行,包含了三級(jí)緩存、屬性注入、初始化,精華都在這20行,下面在源碼中給出了關(guān)鍵注釋。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } // 通過(guò)BeanDefinition實(shí)例化對(duì)象 if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 對(duì)象是否單例、是否未創(chuàng)建完成 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 將對(duì)象的工廠(chǎng)加入到三級(jí)緩存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { // 屬性注入 (在這里解析@Autowired注解時(shí),觸發(fā)循環(huán)依賴(lài)) populateBean(beanName, mbd, instanceWrapper); // 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { // 從緩存中獲取 Bean Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] 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 " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
從緩存中獲取 Bean 的源碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); // 從一級(jí)緩存中獲取 // 如果一級(jí)緩存里沒(méi)有,且 Bean 正在創(chuàng)建中 // 就從二級(jí)緩存里獲取 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // 二級(jí)緩存沒(méi)有,就從三級(jí)緩存獲取一個(gè)工廠(chǎng) if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full sin singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 能獲取到工廠(chǎng)則創(chuàng)建 Bean singletonObject = singletonFactory.getObject(); // 把實(shí)例存入二級(jí)緩存 this.earlySingletonObjects.put(beanName, singletonObject); // 把工廠(chǎng)從三級(jí)緩存移除 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
總之,Spring 的三級(jí)緩存機(jī)制是一個(gè)巧妙的設(shè)計(jì),它解決了在 Bean 初始化過(guò)程中可能出現(xiàn)的循環(huán)依賴(lài)問(wèn)題;對(duì)于 Spring 的用戶(hù)來(lái)說(shuō),這意味著更加穩(wěn)定和可靠的 Bean 管理和依賴(lài)注入機(jī)制。
以上就是Spring通過(guò)三級(jí)緩存解決循環(huán)依賴(lài)問(wèn)題的過(guò)程詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring三級(jí)緩存解決循環(huán)依賴(lài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java gRPC攔截器簡(jiǎn)單實(shí)現(xiàn)分布式日志鏈路追蹤器過(guò)程詳解
有請(qǐng)求的發(fā)送、處理,當(dāng)然就會(huì)有攔截器的需求,例如在服務(wù)端通過(guò)攔截器統(tǒng)一進(jìn)行請(qǐng)求認(rèn)證等操作,這些就需要攔截器來(lái)完成,今天松哥先和小伙伴們來(lái)聊一聊gRPC中攔截器的基本用法,后面我再整一篇文章和小伙伴們做一個(gè)基于攔截器實(shí)現(xiàn)的JWT認(rèn)證的gRPC2023-03-03解決mybatis-plus3.1.1版本使用lambda表達(dá)式查詢(xún)報(bào)錯(cuò)的方法
這篇文章主要介紹了解決mybatis-plus3.1.1版本使用lambda表達(dá)式查詢(xún)報(bào)錯(cuò)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08詳解IDEA 中使用Maven創(chuàng)建項(xiàng)目常見(jiàn)錯(cuò)誤和使用技巧(推薦)
這篇文章主要介紹了詳解IDEA 中使用Maven創(chuàng)建項(xiàng)目常見(jiàn)錯(cuò)誤和使用技巧(推薦),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07JDBC 實(shí)現(xiàn)通用的增刪改查基礎(chǔ)類(lèi)方法
下面小編就為大家分享一篇JDBC 實(shí)現(xiàn)通用的增刪改查基礎(chǔ)類(lèi)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Jenkins自動(dòng)構(gòu)建部署項(xiàng)目到遠(yuǎn)程服務(wù)器上的方法步驟
這篇文章主要介紹了Jenkins自動(dòng)構(gòu)建部署項(xiàng)目到遠(yuǎn)程服務(wù)器上的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01javaweb servlet生成簡(jiǎn)單驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了javaweb servlet生成簡(jiǎn)單驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03