淺析Spring中的循環(huán)依賴問題
Spring的循環(huán)依賴
本文不會詳細(xì)講解 Spring 循環(huán)依賴的基礎(chǔ)問題。
我相信能閱讀到本文的,對 Spring 循環(huán)依賴已經(jīng)有一定了解,但可能存在一些疑惑。本文就是嘗試來解決這些疑惑的。
我們都知道 Spring 是利用了 三級緩存 來解決循環(huán)依賴的,其實(shí)現(xiàn)本質(zhì)是通過提前暴露已經(jīng)實(shí)例化但尚未初始化的 bean 來完成的。
但是呢,我們?nèi)匀粫耄@里為什么要使用三級緩存?而且,我相信,不少人都曾手寫過代碼來解決循環(huán)依賴的問題,那時候,他們也只用了二級緩存,參考下圖:
我們可以仔細(xì)跟蹤序號,理清整個流程。所以,二級緩存是能夠解決循環(huán)依賴,這也符合它的本質(zhì):“提前暴露對象”。這個流程圖并沒有描述接下來的流程,這里使用文字簡單描述下:
- 對象A獲取到已創(chuàng)建完成的對象B注入;
- 對象A完成字段注入以及初始化,并放入一級緩存;
- 對象A從二級緩存中移除;
既然二級緩存能夠解決循環(huán)依賴了,那為什么要使用三級緩存呢?網(wǎng)上的說法是,那是因?yàn)?Spring 中存在替換注入對象的問題。通俗地來說就是:“一個半成品對象有可能在被對象b注入以后,被更改為其它的實(shí)例對象,那么對象b注入的就是一個過期的對象了”。
這種情況會導(dǎo)致對象b注入了一個并不存在于容器中的對象A(因?yàn)楸桓暮蟮膶ο笞⑷肓巳萜?,替換掉了原來的對象)。所以,大多數(shù)人會認(rèn)為三級緩存是為了解決這個問題的,讓我們來看看真的是如此嘛?上代碼:
@Component public class ServiceA { @Autowired private ServiceB serviceB; } @Component public class ServiceB { @Autowired private ServiceA serviceA; } @Component public class ResetServiceABeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(beanName.equals("serviceA")){ return new ServiceA(); } return bean; } }
ServiceA 和 ServiceB 互相依賴,ResetServiceABeanPostProcessor 則是為了在ServiceB 注入了原來的ServiceA 后,將原來的 ServiceA 給替換掉。我們模擬了上述場景,但最后的運(yùn)行結(jié)果卻是得到了一個 BeanCurrentlyInCreationException 異常,異常在圖中的 620 行拋出。
可以發(fā)現(xiàn),似乎它并沒有解決這個“注入了過期對象”的問題,可是它至少檢測出了這個問題。所以,我個人認(rèn)為,三級緩存并不是來解決這個問題,而是來在啟動時檢測這個問題的。
文章寫到這里似乎也差不多了,但我還想糾正一點(diǎn),網(wǎng)上有很多對于三級緩存的描述如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... // 從上至下 分表代表這“三級緩存” private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一級緩存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二級緩存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三級緩存 ... /** Names of beans that are currently in creation. */ // 這個緩存也十分重要:它表示bean創(chuàng)建過程中都會在里面呆著~ // 它在Bean開始創(chuàng)建時放值,創(chuàng)建完成時會將其移出~ private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** Names of beans that have already been created at least once. */ // 當(dāng)這個Bean被創(chuàng)建完成后,會標(biāo)記為這個 注意:這里是set集合 不會重復(fù) // 至少被創(chuàng)建了一次的 都會放進(jìn)這里~~~~ private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); }
但源碼中的順序卻不是如此(我使用的是 5.1.5 版本):
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
但我想三級緩存的分層并不是依賴上面的源碼順序來分為一二三的,而應(yīng)該是根據(jù)從緩存中獲取對象的順序來分層的:
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
最后,我結(jié)合上述的場景來分析下這三個“緩存”中元素的變化(省略了其它無關(guān)流程):
紅色線條代表取出元素,虛線代表最終將不存在。
這里做下總結(jié):
二級緩存也是能解決循環(huán)依賴的,使用三級緩存是為了幫助檢測提前暴露的對象在后期被修改的這種情況;
通過 earlySingletonObjects 持有被暴露的對象,然后在最終返回對象時進(jìn)行比對。如果不是同一個對象,則代表發(fā)生了對象后期被修改的情況。
到此這篇關(guān)于淺析Spring中的循環(huán)依賴問題的文章就介紹到這了,更多相關(guān)Spring的循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA啟動Tomcat時控制臺出現(xiàn)亂碼問題及解決
這篇文章主要介紹了IDEA啟動Tomcat時控制臺出現(xiàn)亂碼問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02springboot獲取微信JSDK簽名信息的實(shí)現(xiàn)示例
本文介紹了如何在Spring Boot應(yīng)用中獲取微信JSDK的簽名信息,包括獲取接口URL、參數(shù)設(shè)置、簽名算法和獲取簽名結(jié)果的步驟,具有一定的參考價值,感興趣的可以了解一下2023-11-11使用HttpClient調(diào)用接口的實(shí)例講解
下面小編就為大家?guī)硪黄褂肏ttpClient調(diào)用接口的實(shí)例講解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10深入分析Comparable與Comparator及Clonable三個Java接口
接口不是類,而是對類的一組需求描述,這些類要遵從接口描述的統(tǒng)一格式進(jìn)行定義,這篇文章主要為大家詳細(xì)介紹了Java的Comparable,Comparator和Cloneable的接口,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-05-05