Spring解決循環依賴的方法(三級緩存)

  說起Spring,作為流水線上裝配工的小碼農,可能是我們最熟悉不過的一種技術框架。但是對於Spring到底是個什麼東西,我猜作為大多數的你可能跟我一樣,隻知道IOC、DI,卻並不明白這其中的原理究竟是怎樣的。在這兒你可能想得完整的關於Spring相關的知識,但是我要告訴你對不起。這裡不是教程,隻能作為你窺探spring核心的窗口。我不做教程,因為網上的教程、源碼解析太多,你可以自行選擇學習。但我要提醒你的是,看再多的教程也不如你一次的主動去追蹤源碼。

  好瞭,廢話說瞭這麼多就是提醒你這裡不是一個教程。隻是一個描繪似的談論,期間會有一些知識或舉例缺陷,所以期望你的指正。

  今天,我們要說的是spring是如何解決循環依賴的。對於一個問題說解決之前,我們首先要先明確形成問題的本因。那麼循環依賴,何為循環依賴呢?

  這裡我們先借用一張圖來通過視覺感受一下,看圖:

  

  其實,通過上面圖片我想你應該能看圖說話瞭,所謂的循環依賴其實就是一種死循環。想象一下生活中的例子就是,你作為一個猛男喜歡一個蘿莉,而蘿莉卻愛上瞭你的基友娘炮,但是娘炮心理卻一直想著和你去澡堂洗澡時撿你扔的肥皂。

  是的,這就是循環依賴的本因。當spring啟動在解析配置創建bean的過程中。首先在初始化A的時候發現需要引用B,然後去初始化B的時候又發現引用瞭C,然後又去初始化C卻發現一個操蛋的結果,C引用瞭A。它又去初始化A一次循環無窮盡,如你們這該死的變態三角關系一樣。

  既然形成瞭這種看起來無法 解決的三角關系,那麼有什麼辦法解決呢?相信聰明的你在面對這樣尷尬的境地,已經開始思考解決方案瞭。想不想的出來沒關系,我們看看spring的大神們是如何來解決這個問題的。

  Spring解決循環依賴的方法就是如題所述的三級緩存、預曝光。

  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為解決循環依賴所定義的三級緩存瞭,那麼我們就來看看它是如何通過這三級緩存來解決這個問題的。

代碼 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正在創建中
            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 是否處於創建中。bean 處於創建中也就是說 bean 在初始化但是沒有完成初始化,有一個這樣的過程其實和 Spring 解決 bean 循環依賴的理念相輔相成,因為 Spring 解決 singleton bean 的核心就在於提前曝光 bean。
  • allowEarlyReference:從字面意思上面理解就是允許提前拿到引用。其實真正的意思是是否允許從 singletonFactories 緩存中通過getObject()拿到對象,為什麼會有這樣一個字段呢?原因就在於 singletonFactories 才是 Spring 解決 singleton bean 的訣竅所在。

好瞭,說道這裡我們來縷清一下當我們執行下面代碼獲取一個name為 user 的bean時Spring都經過瞭怎樣的過程。

代碼 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");

通過追蹤源碼我們發現getBean()方法執行後會調用到AbstractBeanFactory.doGetBean()方法,在此方法中調用瞭我們上文提到的關鍵getSingleton()。因為開始啟動項目,獲取第一個bean時緩存都是空的,所以直接返回一個null。追蹤源碼發現,在doGetBean()後面會調用到AbstractAutowireCapableBeanFactory.doCreateBean()方法進行bean創建,詳細流程就自行追蹤源碼。在這個方法中有一段代碼:

代碼 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 添加到三級緩存
}

通過上面的代碼我們發現,可以提前暴露的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中的三個判斷條件:

  • 單例
  • 允許循環引用的bean
  • 當前 bean 正在創建中

到這裡user這個bean已經創建,但是它還不是一個完整的bean,還需要後續的初始化。但是這不影響其它的bean引用它,假設user為開始圖中的A,那麼當在初始化A(user)的時候,發現A中有一個屬性B,在調用方法applyPropertyValues()去設置這個B的時候。會發現B還沒創建,Spring就會在重復創建A的流程調用doCreate()來創建B,然後添加到三級緩存,設置B的屬性C時,發現C也還沒創建,接著重復前述doCreate()步驟進行C的創建。C創建完成,進行初始化發現C引用瞭A,這時關鍵的地方就是上面的代碼1-2處。

在C設置屬性A的時候,調用getSingleton()獲取bean時,因為A已經在代碼1-4處添加到瞭三級緩存中,C可以直接獲取到A的實例並設置成功後,繼續完成自己創建。初始化完成後,調用如下方法將自己添加到一級緩存。

代碼 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對於循環依賴的解決就說完瞭。總結來看,就想你們三角關系中那樣,娘炮在纏著你撿肥皂的時候,你可以先把肥皂扔到地上安撫一下他。然後,再去追求你要的蘿莉。然而他也可能做出一個假象安撫蘿莉,而蘿莉也可能把你加入雲胎庫。最後的結果要等真正使用時才知道……

到此這篇關於Spring解決循環依賴的的方法(三級緩存)的文章就介紹到這瞭,更多相關Spring循環依賴內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: