Spring解決循環(huán)依賴的方法(三級緩存)
說起Spring,作為流水線上裝配工的小碼農(nóng),可能是我們最熟悉不過的一種技術框架。但是對于Spring到底是個什么東西,我猜作為大多數(shù)的你可能跟我一樣,只知道IOC、DI,卻并不明白這其中的原理究竟是怎樣的。在這兒你可能想得完整的關于Spring相關的知識,但是我要告訴你對不起。這里不是教程,只能作為你窺探spring核心的窗口。我不做教程,因為網(wǎng)上的教程、源碼解析太多,你可以自行選擇學習。但我要提醒你的是,看再多的教程也不如你一次的主動去追蹤源碼。
好了,廢話說了這么多就是提醒你這里不是一個教程。只是一個描繪似的談論,期間會有一些知識或舉例缺陷,所以期望你的指正。
今天,我們要說的是spring是如何解決循環(huán)依賴的。對于一個問題說解決之前,我們首先要先明確形成問題的本因。那么循環(huán)依賴,何為循環(huán)依賴呢?
這里我們先借用一張圖來通過視覺感受一下,看圖:
其實,通過上面圖片我想你應該能看圖說話了,所謂的循環(huán)依賴其實就是一種死循環(huán)。想象一下生活中的例子就是,你作為一個猛男喜歡一個蘿莉,而蘿莉卻愛上了你的基友娘炮,但是娘炮心理卻一直想著和你去澡堂洗澡時撿你扔的肥皂。
是的,這就是循環(huán)依賴的本因。當spring啟動在解析配置創(chuàng)建bean的過程中。首先在初始化A的時候發(fā)現(xiàn)需要引用B,然后去初始化B的時候又發(fā)現(xiàn)引用了C,然后又去初始化C卻發(fā)現(xiàn)一個操蛋的結果,C引用了A。它又去初始化A一次循環(huán)無窮盡,如你們這該死的變態(tài)三角關系一樣。
既然形成了這種看起來無法 解決的三角關系,那么有什么辦法解決呢?相信聰明的你在面對這樣尷尬的境地,已經(jīng)開始思考解決方案了。想不想的出來沒關系,我們看看spring的大神們是如何來解決這個問題的。
Spring解決循環(huán)依賴的方法就是如題所述的三級緩存、預曝光。
Spring的三級緩存主要是singletonObjects、earlySingletonObjects、singletonFactories這三個Map:
代碼 1-1:
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
我們知道了Spring為解決循環(huán)依賴所定義的三級緩存了,那么我們就來看看它是如何通過這三級緩存來解決這個問題的。
代碼 1-2:
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); //首先通過beanName從一級緩存獲取bean if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //如果一級緩存中沒有,并且beanName映射的bean正在創(chuàng)建中 synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); //從二級緩存中獲取 if (singletonObject == null && allowEarlyReference) { //二級緩存也沒有 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); //從三級緩存獲取 if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); //獲取到bean this.earlySingletonObjects.put(beanName, singletonObject); //將獲取的bean提升至二級緩存 this.singletonFactories.remove(beanName); //從三級緩存刪除 } } } } return singletonObject; }
上面的方法就是Spring獲取single bean的過程,其中的一些方法的解釋我就直接借用其它博主的文摘了:
isSingletonCurrentlyInCreation()
:判斷當前 singleton bean 是否處于創(chuàng)建中。bean 處于創(chuàng)建中也就是說 bean 在初始化但是沒有完成初始化,有一個這樣的過程其實和 Spring 解決 bean 循環(huán)依賴的理念相輔相成,因為 Spring 解決 singleton bean 的核心就在于提前曝光 bean。- allowEarlyReference:從字面意思上面理解就是允許提前拿到引用。其實真正的意思是是否允許從 singletonFactories 緩存中通過
getObject()
拿到對象,為什么會有這樣一個字段呢?原因就在于 singletonFactories 才是 Spring 解決 singleton bean 的訣竅所在。
好了,說道這里我們來縷清一下當我們執(zhí)行下面代碼獲取一個name為 user 的bean時Spring都經(jīng)過了怎樣的過程。
代碼 1-3:
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource); UserBean user = (UserBean) factory.getBean("user");
通過追蹤源碼我們發(fā)現(xiàn)getBean()方法執(zhí)行后會調用到AbstractBeanFactory.doGetBean()方法,在此方法中調用了我們上文提到的關鍵getSingleton()。因為開始啟動項目,獲取第一個bean時緩存都是空的,所以直接返回一個null。追蹤源碼發(fā)現(xiàn),在doGetBean()后面會調用到AbstractAutowireCapableBeanFactory.doCreateBean()方法進行bean創(chuàng)建,詳細流程就自行追蹤源碼。在這個方法中有一段代碼:
代碼 1-4:
// 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)); //通過條件判斷該bean是否允許提前曝露 if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); //允許提前暴露的bean 添加到三級緩存 }
通過上面的代碼我們發(fā)現(xiàn),可以提前暴露的bean通過addSingletonFactory()方法添加到了三級緩存SingletonFactories 中,我們看一下它是怎樣操作的。
代碼 1-5:
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); } } }
此時,我們的user這個bean就被加入到了三級緩存中,那么什么樣bean的才會被加入到三級緩存中呢?就是代碼 1-4中的三個判斷條件:
- 單例
- 允許循環(huán)引用的bean
- 當前 bean 正在創(chuàng)建中
到這里user這個bean已經(jīng)創(chuàng)建,但是它還不是一個完整的bean,還需要后續(xù)的初始化。但是這不影響其它的bean引用它,假設user為開始圖中的A,那么當在初始化A(user)的時候,發(fā)現(xiàn)A中有一個屬性B,在調用方法applyPropertyValues()去設置這個B的時候。會發(fā)現(xiàn)B還沒創(chuàng)建,Spring就會在重復創(chuàng)建A的流程調用doCreate()來創(chuàng)建B,然后添加到三級緩存,設置B的屬性C時,發(fā)現(xiàn)C也還沒創(chuàng)建,接著重復前述doCreate()步驟進行C的創(chuàng)建。C創(chuàng)建完成,進行初始化發(fā)現(xiàn)C引用了A,這時關鍵的地方就是上面的代碼1-2處。
在C設置屬性A的時候,調用getSingleton()獲取bean時,因為A已經(jīng)在代碼1-4處添加到了三級緩存中,C可以直接獲取到A的實例并設置成功后,繼續(xù)完成自己創(chuàng)建。初始化完成后,調用如下方法將自己添加到一級緩存。
代碼 1-6:
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
至此,Spring對于循環(huán)依賴的解決就說完了。總結來看,就想你們?nèi)顷P系中那樣,娘炮在纏著你撿肥皂的時候,你可以先把肥皂扔到地上安撫一下他。然后,再去追求你要的蘿莉。然而他也可能做出一個假象安撫蘿莉,而蘿莉也可能把你加入云胎庫。最后的結果要等真正使用時才知道……
到此這篇關于Spring解決循環(huán)依賴的的方法(三級緩存)的文章就介紹到這了,更多相關Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java使用list集合remove需要注意的事項(使用示例)
List集合的一個特點是它其中的元素是有序的,也就是說元素的下標是根據(jù)插入的順序來的,在刪除頭部或者中間的一個元素后,后面的元素下標會往前移動,本文給大家介紹Java使用list集合remove需要注意的事項,感興趣的朋友一起看看吧2022-01-01基于SpringMVC中的路徑參數(shù)和URL參數(shù)實例
這篇文章主要介紹了基于SpringMVC中的路徑參數(shù)和URL參數(shù)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02一文吃透Spring?Cloud?gateway自定義錯誤處理Handler
這篇文章主要為大家介紹了一文吃透Spring?Cloud?gateway自定義錯誤處理Handler方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03