淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎
我們都知道 Spring 是通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴的,但是解決循環(huán)依賴真的需要使用到三級(jí)緩沖嗎?只使用兩級(jí)緩存是否可以呢?本篇文章就 Spring 是如何使用三級(jí)緩存解決循環(huán)依賴作為引子,驗(yàn)證兩級(jí)緩存是否可以解決循環(huán)依賴。
循環(huán)依賴
既然要解決循環(huán)依賴,那么就要知道循環(huán)依賴是什么。如下圖所示:
通過(guò)上圖,我們可以看出:
- A 依賴于 B
- B 依賴于 C
- C 依賴于 A
public class A { private B b; } public class B { private C c; } public class C { private A a; }
這種依賴關(guān)系形成了一種閉環(huán),從而造成了循環(huán)依賴的局面。
下面是未解決循環(huán)依賴的常規(guī)步驟:
- 實(shí)例化 A,此時(shí) A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
- A 對(duì)象發(fā)現(xiàn)需要注入 B 對(duì)象,但是容器中并沒有 B 對(duì)象(如果對(duì)象創(chuàng)建完成并且屬性注入完成和執(zhí)行完初始化方法就會(huì)放入容器中)。
- 實(shí)例化 B,此時(shí) B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
- B 對(duì)象發(fā)現(xiàn)需要注入 C 對(duì)象,但是容器中并沒有 C 對(duì)象。
- 實(shí)例化 C,此時(shí) C 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行。
- C 對(duì)象發(fā)現(xiàn)需要注入 A 對(duì)象,但是容器中并沒有 A 對(duì)象。
- 重復(fù)步驟 1。
三級(jí)緩存
Spring 解決循環(huán)依賴的核心就是提前暴露對(duì)象,而提前暴露的對(duì)象就是放置于第二級(jí)緩存中。下表是三級(jí)緩存的說(shuō)明:
名稱 | 描述 |
---|---|
singletonObjects | 一級(jí)緩存,存放完整的 Bean。 |
earlySingletonObjects | 二級(jí)緩存,存放提前暴露的Bean,Bean 是不完整的,未完成屬性注入和執(zhí)行 init 方法。 |
singletonFactories | 三級(jí)緩存,存放的是 Bean 工廠,主要是生產(chǎn) Bean,存放到二級(jí)緩存中。 |
所有被 Spring 管理的 Bean,最終都會(huì)存放在 singletonObjects 中,這里面存放的 Bean 是經(jīng)歷了所有生命周期的(除了銷毀的生命周期),完整的,可以給用戶使用的。
earlySingletonObjects 存放的是已經(jīng)被實(shí)例化,但是還沒有注入屬性和執(zhí)行 init 方法的 Bean。
singletonFactories 存放的是生產(chǎn) Bean 的工廠。
Bean 都已經(jīng)實(shí)例化了,為什么還需要一個(gè)生產(chǎn) Bean 的工廠呢?這里實(shí)際上是跟 AOP 有關(guān),如果項(xiàng)目中不需要為 Bean 進(jìn)行代理,那么這個(gè) Bean 工廠就會(huì)直接返回一開始實(shí)例化的對(duì)象,如果需要使用 AOP 進(jìn)行代理,那么這個(gè)工廠就會(huì)發(fā)揮重要的作用了,這也是本文需要重點(diǎn)關(guān)注的問題之一。
解決循環(huán)依賴
Spring 是如何通過(guò)上面介紹的三級(jí)緩存來(lái)解決循環(huán)依賴的呢?這里只用 A,B 形成的循環(huán)依賴來(lái)舉例:
- 實(shí)例化 A,此時(shí) A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,A 只是一個(gè)半成品。
- 為 A 創(chuàng)建一個(gè) Bean 工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) A 需要注入 B 對(duì)象,但是一級(jí)、二級(jí)、三級(jí)緩存均為發(fā)現(xiàn)對(duì)象 B。
- 實(shí)例化 B,此時(shí) B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,B 只是一個(gè)半成品。
- 為 B 創(chuàng)建一個(gè) Bean 工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) B 需要注入 A 對(duì)象,此時(shí)在一級(jí)、二級(jí)未發(fā)現(xiàn)對(duì)象 A,但是在三級(jí)緩存中發(fā)現(xiàn)了對(duì)象 A,從三級(jí)緩存中得到對(duì)象 A,并將對(duì)象 A 放入二級(jí)緩存中,同時(shí)刪除三級(jí)緩存中的對(duì)象 A。(注意,此時(shí)的 A 還是一個(gè)半成品,并沒有完成屬性填充和執(zhí)行初始化方法)
- 將對(duì)象 A 注入到對(duì)象 B 中。
- 對(duì)象 B 完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 B。(此時(shí)對(duì)象 B 已經(jīng)是一個(gè)成品)
- 對(duì)象 A 得到對(duì)象 B,將對(duì)象 B 注入到對(duì)象 A 中。(對(duì)象 A 得到的是一個(gè)完整的對(duì)象 B)
- 對(duì)象 A 完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 A。
我們從源碼中來(lái)分析整個(gè)過(guò)程:
創(chuàng)建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // ① 實(shí)例化對(duì)象 instanceWrapper = this.createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null; Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null; // ② 判斷是否允許提前暴露對(duì)象,如果允許,則直接添加一個(gè) ObjectFactory 到三級(jí)緩存 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 添加三級(jí)緩存的方法詳情在下方 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // ③ 填充屬性 this.populateBean(beanName, mbd, instanceWrapper); // ④ 執(zhí)行初始化方法,并創(chuàng)建代理 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }
添加三級(jí)緩存的方法如下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象 this.singletonFactories.put(beanName, singletonFactory); // 添加至三級(jí)緩存 this.earlySingletonObjects.remove(beanName); // 確保二級(jí)緩存沒有此對(duì)象 this.registeredSingletons.add(beanName); } } } @FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; }
通過(guò)這段代碼,我們可以知道 Spring 在實(shí)例化對(duì)象的之后,就會(huì)為其創(chuàng)建一個(gè) Bean 工廠,并將此工廠加入到三級(jí)緩存中。
因此,Spring 一開始提前暴露的并不是實(shí)例化的 Bean,而是將 Bean 包裝起來(lái)的 ObjectFactory。為什么要這么做呢?
這實(shí)際上涉及到 AOP,如果創(chuàng)建的 Bean 是有代理的,那么注入的就應(yīng)該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始并不知道 Bean 是否會(huì)有循環(huán)依賴,通常情況下(沒有循環(huán)依賴的情況下),Spring 都會(huì)在完成填充屬性,并且執(zhí)行完初始化方法之后再為其創(chuàng)建代理。但是,如果出現(xiàn)了循環(huán)依賴的話,Spring 就不得不為其提前創(chuàng)建代理對(duì)象,否則注入的就是一個(gè)原始對(duì)象,而不是代理對(duì)象。因此,這里就涉及到應(yīng)該在哪里提前創(chuàng)建代理對(duì)象?
Spring 的做法就是在 ObjectFactory 中去提前創(chuàng)建代理對(duì)象。它會(huì)執(zhí)行 getObject() 方法來(lái)獲取到 Bean。實(shí)際上,它真正執(zhí)行的方法如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 如果需要代理,這里會(huì)返回代理對(duì)象;否則返回原始對(duì)象 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
因?yàn)樘崆斑M(jìn)行了代理,避免對(duì)后面重復(fù)創(chuàng)建代理對(duì)象,會(huì)在 earlyProxyReferences 中記錄已被代理的對(duì)象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 記錄已被代理的對(duì)象 this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } }
通過(guò)上面的解析,我們可以知道 Spring 需要三級(jí)緩存的目的是為了在沒有循環(huán)依賴的情況下,延遲代理對(duì)象的創(chuàng)建,使 Bean 的創(chuàng)建符合 Spring 的設(shè)計(jì)原則。
如何獲取依賴
我們目前已經(jīng)知道了 Spring 的三級(jí)依賴的作用,但是 Spring 在注入屬性的時(shí)候是如何去獲取依賴的呢?
他是通過(guò)一個(gè) getSingleton() 方法去獲取所需要的 Bean 的。
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 一級(jí)緩存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 二級(jí)緩存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 三級(jí)緩存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // Bean 工廠中獲取 Bean singletonObject = singletonFactory.getObject(); // 放入到二級(jí)緩存中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
當(dāng) Spring 為某個(gè) Bean 填充屬性的時(shí)候,它首先會(huì)尋找需要注入對(duì)象的名稱,然后依次執(zhí)行 getSingleton() 方法得到所需注入的對(duì)象,而獲取對(duì)象的過(guò)程就是先從一級(jí)緩存中獲取,一級(jí)緩存中沒有就從二級(jí)緩存中獲取,二級(jí)緩存中沒有就從三級(jí)緩存中獲取,如果三級(jí)緩存中也沒有,那么就會(huì)去執(zhí)行 doCreateBean() 方法創(chuàng)建這個(gè) Bean。
二級(jí)緩存
我們現(xiàn)在已經(jīng)知道,第三級(jí)緩存的目的是為了延遲代理對(duì)象的創(chuàng)建,因?yàn)槿绻麤]有依賴循環(huán)的話,那么就不需要為其提前創(chuàng)建代理,可以將它延遲到初始化完成之后再創(chuàng)建。
既然目的只是延遲的話,那么我們是不是可以不延遲創(chuàng)建,而是在實(shí)例化完成之后,就為其創(chuàng)建代理對(duì)象,這樣我們就不需要第三級(jí)緩存了。因此,我們可以將addSingletonFactory() 方法進(jìn)行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象 object o = singletonFactory.getObject(); // 直接從工廠中獲取 Bean this.earlySingletonObjects.put(beanName, o); // 添加至二級(jí)緩存中 this.registeredSingletons.add(beanName); } } }
這樣的話,每次實(shí)例化完 Bean 之后就直接去創(chuàng)建代理對(duì)象,并添加到二級(jí)緩存中。測(cè)試結(jié)果是完全正常的,Spring 的初始化時(shí)間應(yīng)該也是不會(huì)有太大的影響,因?yàn)槿绻?Bean 本身不需要代理的話,是直接返回原始 Bean 的,并不需要走復(fù)雜的創(chuàng)建代理 Bean 的流程。
結(jié)論
測(cè)試證明,二級(jí)緩存也是可以解決循環(huán)依賴的。為什么 Spring 不選擇二級(jí)緩存,而要額外多添加一層緩存呢?
如果 Spring 選擇二級(jí)緩存來(lái)解決循環(huán)依賴的話,那么就意味著所有 Bean 都需要在實(shí)例化完成之后就立馬為其創(chuàng)建代理,而 Spring 的設(shè)計(jì)原則是在 Bean 初始化完成之后才為其創(chuàng)建代理。所以,Spring 選擇了三級(jí)緩存。但是因?yàn)檠h(huán)依賴的出現(xiàn),導(dǎo)致了 Spring 不得不提前去創(chuàng)建代理,因?yàn)槿绻惶崆皠?chuàng)建代理對(duì)象,那么注入的就是原始對(duì)象,這樣就會(huì)產(chǎn)生錯(cuò)誤。
到此這篇關(guān)于淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎的文章就介紹到這了,更多相關(guān)Spring 循環(huán)依賴三級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼
本文主要介紹了SpringBoot實(shí)現(xiàn)接口等冪次校驗(yàn)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01Springboot3.3 整合Cassandra 4.1.5的詳細(xì)過(guò)程
這篇文章主要介紹了Springboot3.3 整合Cassandra 4.1.5的詳細(xì)過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06JAVA系統(tǒng)中Spring?Boot應(yīng)用程序的配置文件application.yml使用詳解
這篇文章主要介紹了JAVA系統(tǒng)中Spring?Boot應(yīng)用程序的配置文件application.yml的相關(guān)資料,application.yml是Spring?Boot應(yīng)用程序的配置文件,定義了服務(wù)器、Spring、日志、安全及其他配置屬性,確保應(yīng)用程序正確啟動(dòng)和運(yùn)行,需要的朋友可以參考下2025-01-01Java8 Stream中對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值的代碼示例
這篇文章主要介紹了Java8 Stream中如何對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值,文中通過(guò)代碼示例為大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-06-06Java如何將int型數(shù)組轉(zhuǎn)為String型數(shù)組
這篇文章主要介紹了Java如何將int型數(shù)組轉(zhuǎn)為String型數(shù)組,本文給大家分享具體實(shí)現(xiàn)思路結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性
這篇文章主要為大家介紹了Springboot項(xiàng)目通過(guò)redis實(shí)現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12