1. 程式人生 > 實用技巧 >Spring是怎麼解決迴圈依賴的? (裝載)

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 迴圈依賴的情況有三種:

  1. 構造器的迴圈依賴。 Spring 是無法解決的,只能丟擲BeanCurrentlyInCreationException異常表示迴圈依賴
  2. field 屬性的迴圈依賴(setter) spring支援的預設方式,使用到spring快取
  3. 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 解決的過程:

  1.   首先 A 完成初始化第一步並將自己提前曝光出來(通過 三級快取 將自己提前曝光),在初始化的時候,發現自己依賴物件 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建出來
  2.   然後 B 就走建立流程,在 B 初始化的時候,同樣發現自己依賴 C,C 也沒有被創建出來
  3.   這個時候 C 又開始初始化程序,但是在初始化的過程中發現自己依賴 A,於是嘗試 get(A),這個時候由於 A 已經新增至快取中(三級快取 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過ObjectFactory#getObject()方法來拿到 A 物件,C 拿到 A 物件後順利完成初始化,然後將自己新增到一級快取中
  4.   回到 B ,B 也可以拿到 C 物件,完成初始化,A 可以順利拿到 B 完成初始化。到這裡整個鏈路就已經完成了初始化過程了

  最後,為什麼多例模式不能解決迴圈依賴呢?
  因為多例模式下每次new() Bean都不是一個,如果按照這樣存到快取中,就變成單例了。

轉載自:http://www.662p.com/article/793.html