Spring是怎麼解決迴圈依賴的? (裝載)
首先回顧下Bean載入的主流程:
1.如果是單例模式,從factoryBeanInstanceCache 快取中獲取BeanWrapper 例項物件並刪除快取
2.呼叫createBeanInstance()例項化 bean
3.後置處理
4.單例模式的迴圈依賴處理
5.屬性填充
6.初始化 bean 例項物件
7.依賴檢查
8.註冊 DisposableBean
這裡我們主要分析第4步(單例模式的迴圈依賴處理):
一、迴圈依賴是什麼?
迴圈依賴,其實就是迴圈引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。如下圖所示:
Spring中的迴圈依賴,其實就是一個死迴圈的過程,在初始化 A 的時候發現依賴了 B,這時就會去初始化 B,然後又發現 B 依賴 C,跑去初始化 C,初始化 C 的時候發現依賴了 A,則又會去初始化 A,依次迴圈永不退出,除非有終結條件。
一般來說,Spring 迴圈依賴的情況有三種:
- 構造器的迴圈依賴。 Spring 是無法解決的,只能丟擲BeanCurrentlyInCreationException異常表示迴圈依賴
- field 屬性的迴圈依賴(setter) spring支援的預設方式,使用到spring快取
- setter原型模式(prototype) Spring 是無法解決的,也會跑出異常
為什麼 Spring 不處理 prototype bean 呢?其實如果理解 Spring 是如何解決 singleton bean 的迴圈依賴就明白了。這裡先留個疑問,我們先來看下 Spring 是如何解決 singleton bean 的迴圈依賴的。
二、解決singleton迴圈依賴
在AbstractBeanFactory 的doGetBean()方法中,我們根據BeanName去獲取Singleton Bean的時候,會先從快取獲取。
1 //DefaultSingletonBeanRegistry.java@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {2 // 從一級快取快取 singletonObjects 中載入 bean 3 Object singletonObject = this.singletonObjects.get(beanName); 4 // 快取中的 bean 為空,且當前 bean 正在建立 5 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 6 // 加鎖 7 synchronized (this.singletonObjects) { 8 // 從 二級快取 earlySingletonObjects 中獲取 9 singletonObject = this.earlySingletonObjects.get(beanName); 10 // earlySingletonObjects 中沒有,且允許提前建立 11 if (singletonObject == null && allowEarlyReference) { 12 // 從 三級快取 singletonFactories 中獲取對應的 ObjectFactory 13 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 14 if (singletonFactory != null) { 15 //從單例工廠中獲取bean 16 singletonObject = singletonFactory.getObject(); 17 // 新增到二級快取 18 this.earlySingletonObjects.put(beanName, singletonObject); 19 // 從三級快取中刪除 20 this.singletonFactories.remove(beanName); 21 } 22 } 23 } 24 } 25 return singletonObject; 26 }
這段程式碼涉及的3個關鍵的變數,分別是3個級別的快取,定義如下:
/** Cache of singleton objects: bean name --> bean instance */ //單例bean的快取 一級快取 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 */ //預載入單例bean快取 二級快取 //存放的 bean 不一定是完整的 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
getSingleton()的邏輯比較清晰:
- 首先,嘗試從一級快取singletonObjects中獲取單例Bean
- 如果獲取不到,則從二級快取earlySingletonObjects中獲取單例Bean。
- 如果仍然獲取不到,則從三級快取singletonFactories中獲取單例BeanFactory。
- 最後,如果從三級快取中拿到了BeanFactory,則通過getObject()把Bean存入二級快取中,並把該Bean的三級快取刪掉。
2.1、三級快取
看到這裡可能會有些疑問,這3個快取怎麼就解決了singleton迴圈依賴了呢?
先彆著急,我們現在分析了獲取快取的程式碼,再來看下儲存快取的程式碼。 在AbstractAutowireCapableBeanFactory的doCreateBean()方法中,有這麼一段程式碼:
1 // AbstractAutowireCapableBeanFactory.javaboolean earlySingletonExposure = (mbd.isSingleton() 2 // 單例模式 3 && this.allowCircularReferences 4 // 允許迴圈依賴 5 && isSingletonCurrentlyInCreation(beanName)); 6 // 當前單例 bean 是否正在被建立if (earlySingletonExposure) { 7 if (logger.isTraceEnabled()) { 8 logger.trace("Eagerly caching bean '" + beanName + 9 "' to allow for resolving potential circular references"); 10 } 11 // 為了後期避免迴圈依賴,提前將建立的 bean 例項加入到三級快取 singletonFactories 中 12 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 13 }
這段程式碼就是put三級快取singletonFactories的地方,其核心邏輯是,當滿足以下3個條件時,把bean加入三級快取中:
- 單例
- 允許迴圈依賴
- 當前單例Bean正在建立
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)方法,程式碼如下:
// DefaultSingletonBeanRegistry.javaprotected 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); } } }
從這段程式碼我們可以看出,singletonFactories 這個三級快取才是解決 Spring Bean 迴圈依賴的關鍵。同時這段程式碼發生在createBeanInstance(...)方法之後,也就是說這個 bean 其實已經被創建出來了,但是它還沒有完善(沒有進行屬性填充和初始化),但是對於其他依賴它的物件而言已經足夠了(已經有記憶體地址了,可以根據物件引用定位到堆中物件),能夠被認出來了。
2.2、一級快取
到這裡我們發現三級快取 singletonFactories 和 二級快取 earlySingletonObjects 中的值都有出處了,那一級快取在哪裡設定的呢?在類 DefaultSingletonBeanRegistry 中,可以發現這個addSingleton(String beanName, Object singletonObject)方法,程式碼如下:
1 // DefaultSingletonBeanRegistry.javaprotected void addSingleton(String beanName, Object singletonObject) { 2 synchronized (this.singletonObjects) { 3 //新增至一級快取,同時從二級、三級快取中刪除。 4 this.singletonObjects.put(beanName, singletonObject); 5 this.singletonFactories.remove(beanName); 6 this.earlySingletonObjects.remove(beanName); 7 this.registeredSingletons.add(beanName); 8 } 9 }
該方法是在 #doGetBean(...) 方法中,處理不同 scope 時,如果是 singleton呼叫的,如下圖所示:
也就是說,一級快取裡面是完整的Bean。
小結:
- 一級快取裡面是完整的Bean,是當一個Bean完全建立後才put
- 三級快取是不完整的BeanFactory,是當一個Bean在new之後就put(沒有屬性填充、初始化)
- 二級快取是對三級快取的易用性處理,只不過是通過getObject()方法從三級快取的BeanFactory中取出Bean
總結:
現在我們再來回顧下Spring解決單例迴圈依賴的方案:
- Spring 在建立 bean 的時候並不是等它完全完成,而是在建立過程中將建立中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三級快取中)。
- 這樣,一旦下一個 bean 建立的時候需要依賴 bean ,則從三級快取中獲取。
舉個栗子:
比如我們團隊裡要報名參加活動,你不用上來就把你的生日、性別、家庭資訊什麼的全部填完,你只要先報個名字,統計下人數就行,之後再慢慢完善你的個人資訊。
核心思想:提前暴露,先用著
最後來描述下就上面那個迴圈依賴 Spring 解決的過程:
- 首先 A 完成初始化第一步並將自己提前曝光出來(通過 三級快取 將自己提前曝光),在初始化的時候,發現自己依賴物件 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建出來
- 然後 B 就走建立流程,在 B 初始化的時候,同樣發現自己依賴 C,C 也沒有被創建出來
- 這個時候 C 又開始初始化程序,但是在初始化的過程中發現自己依賴 A,於是嘗試 get(A),這個時候由於 A 已經新增至快取中(三級快取 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過ObjectFactory#getObject()方法來拿到 A 物件,C 拿到 A 物件後順利完成初始化,然後將自己新增到一級快取中
- 回到 B ,B 也可以拿到 C 物件,完成初始化,A 可以順利拿到 B 完成初始化。到這裡整個鏈路就已經完成了初始化過程了
最後,為什麼多例模式不能解決迴圈依賴呢?
因為多例模式下每次new() Bean都不是一個,如果按照這樣存到快取中,就變成單例了。