《系列二》-- 1、BeanFactory.getBean 總覽

2023-06-24 12:00:21

閱讀之前要注意的東西:本文就是主打流水賬式的原始碼閱讀,主導的是一個參考,主要內容需要看官自己去原始碼中驗證。全系列文章基於 spring 原始碼 5.x 版本。

寫在開始前的話:

閱讀spring 原始碼實在是一件龐大的工作,不說全部內容,單就最基本核心部分包含的東西就需要很長時間去消化了:

  • beans
  • core
  • context

實際上我在部落格里貼出來的還只是一部分內容,更多的內容,我放在了個人,fork自 spring 官方原始碼倉了; 而且對原始碼的學習,必須是要跟著實際程式碼層層遞進的,不然只是乾巴巴的文字味同嚼蠟。

https://gitee.com/bokerr/spring-framework-5.0.x-study

這個倉設定的公共倉,可以直接拉取。



Spring原始碼閱讀系列--全域性目錄.md



一句話概括

本文以走馬觀花的姿態,簡單分析了一波 spring 容器建立bean的大致環節,後續將通過單獨的文章進行細講其中的沒個環節。

本文將包含後文的連結,根據感興趣的內容自取即可。

1 書接上回

我們已經知道了spring 是怎麼解析標籤的。

現在我們解析完標籤並註冊到 BeanFactoryRegistry 介面進行管理了,接下來我們要用解析的結果建立bean了

記得開篇說的那兩行程式碼麼:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class XmlBeanFactoryTest {
	BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
	Object object = beanFactory.getBean("action");
}

現在正式進入第二行程式碼的流程:

Object object = beanFactory.getBean("action");

同理 【Shift + Ctrl + 滑鼠左鍵】 嘗試找到 getBean 的實現類,這裡發現有多個符合條件的實現類;

再解析 XmlBeanFactory 的類圖,那麼顯而易見,我們應該看 AbstractBeanFactory 類的 getBean() 方法

然後再層層遞進,我們來到了 AbstractBeanFactory.doGetBean() 方法,這個方法的方法體非常長,如果不考慮其中細節,我們只需要講解這一個方法大致就可以知道 BeanFactory.getBean() 最基本的實現,及其重要環節。

2 揭開 doGetBean() 的神祕面紗, 重要操作一覽

實際上在spring 的5.x版本中, doGetBean()方法體非常的長, 應該在 150 行左右了,或許螢幕前的你會嗤之以鼻,就這?

如果平時有嚴格講究clean code 規範,那麼 超過 50 行的方法都是不合理的。(剛畢業時在某家公司見過 2700 多行的方法,雖然離職許久了,但是我敢肯定時至今日,那些程式碼必定還在"成長"。)

下邊我會貼一串處理後的虛擬碼,主要用來展示 doGetGean() 方法的骨架:


protected <T> T doGetBean(
    String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {
    // 1. 轉換/提取beanName  name 可能為:FactoryBean 、 別名alias 等
    
    String beanName = transformedBeanName(name);
    Object bean;
    
    // 2. 嘗試直接從快取中獲取,或者從 singletonFactories 中的 ObjectFactory 獲取
    
    //  - 檢查快取中或者範例工廠中是否有對應範例
    Object sharedInstance = getSingleton(beanName);  // 第一次嘗試從單例bean池中獲取快取的 bean 範例
    if (sharedInstance != null && args == null) {
        // && 2.1 如果 bean 原型是FactoryBean 型別,則通過其 getObject() 方法生成真正的 bean 
        //    && 2.2 否則直接返回 sharedInstance 本身作為: 待獲取的bean
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {

        // 3. 快取中不存在,那麼就要從頭再來了, 從前文 xml 標籤解析到的 BeanDefinition 開始,逐步生成真正的 bean 物件    
        // 3.1 存在無法消解的迴圈參照
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    
        // 3.2 parentBeanFactory 檢查 
        // parentBeanFactory 不為空  && 當前載入的XML中不包含 beanName 對映的類時,會嘗試從 parentBeanFactory 獲取
        BeanFactory parentBeanFactory = getParentBeanFactory();
        if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
            // 3.2.1 當前工廠沒能獲取到相關 bean 向 parentBeanFactory 詢問
            String nameToLookup = originalBeanName(name);
            if (parentBeanFactory instanceof AbstractBeanFactory) { 
                return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
            } else if (args != null) {
                return (T) parentBeanFactory.getBean(nameToLookup, args);
            } else {
                return parentBeanFactory.getBean(nameToLookup, requiredType);
            }
        }
        
        // 4. 暫停一下, 能走到這一步說明:
        // 4.1 第一,從快取取不到,那麼是第一次載入當前bean; 
        // 4.2 第二,沒有進入上述的 parentBeanFactory 的流程裡,說明 當前 的BeanFactory 裡有帶獲取bean 的定義。
    
        // 5 類比 * 提前暴露,如果該設定生效,通過檢查則,視為該 bean 已經建立
        if (!typeCheckOnly) {
            // 標記該 bean 已經被建立過
            markBeanAsCreated(beanName);
        }
    
        // 6. 正式進入,從零開始建立一個 bean 的過程
        try {
            // 6.1 對 bean 的後續處理都是針對 RootBeanDefinition 進行的,所以需要:
            // 將儲存的 XML 組態檔的 GenericBeanDefinition 轉化為 RootBeanDefinition  -->  ChildBeanDefinition
            RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);  // 老熟人了,第一個系列的文章講的就是怎麼得到她
            
            // 6.2 校驗  mbd 對應的類是否為抽象類,[ 當前程式碼版本: spring 5.1 ]
            checkMergedBeanDefinition(mbd, beanName, args);
    
            // Guarantee initialization of beans that the current bean depends on.
            // 6.3 獲取參照關係, 範例化依賴的 bean [需要spring容器注入的依賴]
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    if (isDependent(beanName, dep)) { //  無法處理的, 迴圈依賴拋異常 TODO
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    registerDependentBean(dep, beanName);  //  記錄bean的name 記錄他們之間的參照關係,被銷燬時根據關係去銷燬
                    try {
                        getBean(dep);  // 遞迴建立當前bean,依賴的其它 bean
                    } catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }
    
            // 7. 建立bean自身
            // 它依賴的 bean [ 載入/建立 ] 完畢, 最終到了載入其本身的時刻 (根據它的 beanDefinition 建立真正的 bean 範例)
            if (mbd.isSingleton()) {  
                // 7.1 單例模式
                // 第二次嘗試從單例bean快取池中獲取該bean範例;若無法從快取中獲取(未被載入),從頭開始載入該 bean 的範例
                // 具體載入行為由 createBean()方法負責
                // 回撥 getObject().createBean()前: 範例化前的準備工作 -- before[PostProcessor(後置處理器)]
                // 回撥 getObject().createBean()後: 範例化完後的補充工作 -- after[PostProcessor(後置處理器)]
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            // 準備完成後回撥  由子類 AbstractAutowireCapableBeanFactory 實現方法
                            return createBean(beanName, mbd, args);
                        } catch (BeansException ex) {
                            // 出錯,單例工廠銷燬該 bean
                            destroySingleton(beanName);
                        }
                    }
                });
                // 返回對應的範例  有些情況並不直接返回 範例本身,而是返回   <指定方法>   <返回的範例>
                // <指定方法> : 實現  《特定工廠介面》  的   《工廠範例》   的某個方法
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            } else if (mbd.isPrototype()) {
                // 7.2 prototype 原型模式, 範例化的手段: 直接建立新的bean
                Object prototypeInstance = null;
                try {
                    //  7.2.1 標記 bean 正在被建立
                    beforePrototypeCreation(beanName);
                    // 7.2.2 進入建立流程
                    prototypeInstance = createBean(beanName, mbd, args);
                } finally {
                    // 7.2.3 標記的取消: 正在建立 (7.2.1 的逆向操作)
                    afterPrototypeCreation(beanName);
                }
                // 返回對應的bean範例,如果是 FactoryBean 時, 返回其 getObject() 方法的返回值  TODO 
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            } else {
                // 根據設定的 scope 範例化 bean (除單例-singleton、原型-prototype 之外)
                String scopeName = mbd.getScope();
                Scope scope = this.scopes.get(scopeName);                
                try {
                    Object scopedInstance = scope.get(beanName, () -> {
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        } finally {
                            afterPrototypeCreation(beanName);
                        }
                    });
                    // 返回對應的範例  有些情況並不直接返回 範例本身,而是返回   <指定方法>   <返回的範例>
                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                } catch (IllegalStateException ex) { }
            }
        } catch (BeansException ex) {
            // 發生異常後,清理現場
            cleanupAfterBeanCreationFailure(beanName);
        }
    }

    // 檢查生成的bean型別是否符合實際需求
    if (requiredType != null && !requiredType.isInstance(bean)) {
        try {
            T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
            return convertedBean;
        } catch (TypeMismatchException ex) {
            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
        }
    }
    return (T) bean;
}

關鍵詞:

createBean(beanName, mbd, args);

文中一共三處,你可以仔細觀察這三處 createBean() 所處的語境:

  • 第一次是 單例的場景, createBean() 方法的返回值,被一個單例處理的方法接收。它會根據 bean 的名稱判斷是否已經範例化過了,這裡為了保證單例,甚至還用了雙重鎖機制。

  • 第二次是原型模式, 也就是建立物件時直接new, 你看這裡 createBean() 的上下文語境,沒有被任何的校驗操作環繞,直接就返回了。

  • 第三次出現,是為了範例化上述兩種型別之外的 bean, 這裡 createBean() 被 Scope.get() 方法接收,可以確定的是這裡必定會發生,根據 bean的Scope作用域而進行的校驗。

沒錯上邊展示的就是 "骨架", 往簡單了說,getBean() 所做的事情不外乎上邊提到的內容。

但是實際上呢,createBean() 的過程中還有許許多多重要的操作需要一一道來,後續章節將圍繞上述的 "骨架" 進行展開。

後邊的章節可以看作是一個目錄,如果內容比較多,我會通過一個外部連結單獨展示相關的內容。若篇幅較為短小,則會直接在下邊呈現。

3 必要的課外知識

3.1 bean 的作用域 Scope 有哪些

2、bean 的作用域 Scope 有哪些

3.2 FactoryBean 是什麼

3、FactoryBean的使用

3.3 什麼是迴圈依賴

4、迴圈依賴及其消解

4 深入瞭解 doGetBean

第三節介紹的內容將服務於本節接下來的內容,接下來我們將回顧 doGetBean 的整個流程,然後對如下列出的環節進行或詳細或粗略的介紹。

1) 深入瞭解 doGetBean() 之 嘗試從快取 [單例] 讀取bean

5、單例bean的獲取

2) bean 與 FactoryBean

原始碼中找到如下這行程式碼:

假如上一步中,我們從快取中讀取到了單例bean, 本小節將延續這條路線。

前邊講 FactoryBean 的時候就提到它了.

這裡進去做的事就比較好理解了:
1 判斷 bean 型別:如果不是 FactoryBean,或者bean命名不符合 FactoryBean 的命名格式,方法返回

2 若是 FactoryBean,先嚐試從快取獲取( 快取中儲存的是:FactoryBean.getObject() 生成好的bean );從快取獲取失敗後進入後續流程

3 判斷是否單例,如果是單例場景又是一套組合拳:鎖定 singletonObjects 容器,然後進入從FactoryBean呼叫 getObject() 獲取bean;
getObject() 後再次嘗試讀取快取,若獲取到了則捨棄上述讀取動作的新的bean,以快取中的為舊bean作為最終結果;而後進入bean的後處理器的增強操作流程;
而後方法返回前將載入結果放到單例快取容器: factoryBeanObjectCache 中。需要注意的是:容器 factoryBeanObjectCache 僅僅為單例場景服務,因為 "多例" 場景下根本不需要快取,每次直接從 FactoryBean 獲取全新的 bean即可。

【其實這裡也回答了,介紹 FactoryBean那篇文章中末尾遺留的問題:我們並不需要在 FactoryBean 的內部實現單例,spring 容器已經幫我們實現了,FactoryBean的單例管理。 】

4 若不是單例,無需考慮全域性唯一,直接從 FactoryBeab 獲取全新 bean;接著判斷是否需要後置處理器的增強.... 至此流程結束。。。。。

實際上到目前位置的邏輯處理的都是,從快取中成功獲取到了bean的流程。

後續章節中,將圍繞從 0 開始建立bean 的流程展開。

3) 深入瞭解 doGetBean() 之 ParentBeanFactory

這裡邏輯其實挺簡單的:

  • 1 標記的第一處:其實就是判斷 bean 是否已經進入建立流程,isPrototypeCurrentlyInCreation 內部其實就是一個 ThreadLocal 容器,如果你想深入追蹤它,
    你可以關注這個 ThreadLocal 容器的set() 方法的呼叫鏈路,其實不難發現它的set 方法的呼叫,同樣是在 doGetBean() 的後續環節中。
    這裡如果我們討論的是一個全新bean的建立,那麼肯定就不存在衝突。

  • 2 標記的第二處: 嘗試獲取當前 BeanFactory 的 "父工廠", 然後判斷當前 BeanFactory 的 BeanDefinitionRegistry 是否有被請求bean 的定義;
    如果當前BeanFactory 中不包含該 bean的定義,且 "父工廠" 不為空時,bean 的載入動作將被轉嫁給 "父工廠" 去執行。

很明顯,這裡用 beanName 呼叫了 parentBeanFactory 的 getBean() 、 doGetBean() 等方法,這裡就可以視作套娃遞迴了。

當然如果符合上述的情形,我們當前的分析就到頭。

如果時不走 「父工廠」 的情形,我們接著往下看。

4) 深入瞭解 doGetBean() 之 BeanDefinition 物件的處理

BeanDefinition: 如果你瞭解過 spring 對 xml 檔案的載入,我想你對它不會陌生吧。實際上 spring 對xml 檔案解析的結果,就是以 BeanDefinition 的形式進行儲存的。

如果你還沒忘記,我們當前仍然在 XmlBeanFactory().getBean() 的流程裡。我們的大前提是:通過前邊章節的介紹,XmlBeanFactory 已經完成了 對 xml 組態檔的解析,並通過 BeanDefinitionRegistry 介面進行管理。

如果忘了,我們再把 XmlBeanFactory 的類圖放出來瞅瞅:

接著看原始碼:

這幾行程式碼也比較幹練,這裡做的幾件事無非就是根據 beanName,從BeanDefinitionRegistry 介面讀取, 相應的 BeanDefinition,並進行一定的轉化:

  • 1 如果從 BeanDefinition 中發現,它存在父類別,那麼會去讀取父類別,最終並用子類屬性去覆蓋父類別的 BeanDefinition, 得到該bean的最終定義。
  • 2 對於上述裝換操作完成的bean, 還可以通過容器快取起來,若該bean 再次被載入時就可以重複利用了。( "多例"bean 很容易被多次載入)
  • 3 校驗該 beanName 指向的是否是抽象類,如果是,丟擲異常
  • 4 從 BeanDefinition 獲取,它依賴的別的 bean, 如果有依賴的bean,那麼就轉向被依賴的bean的載入,上圖倒數第四行,承接的就是該操作;
    事實上bean載入時,都是要先去載入其依賴的bean的,它會遞迴的去載入所有被依賴的 bean,直至所有bean都被載入為止。
    【其實前邊講迴圈依賴,時提到過這個概念的。】

如果當前bean 依賴了別的bean,那麼沒什麼好說的,這裡就轉向被依賴bean的載入。

如果被依賴bean全部被載入完了,或者它就不依賴任何的其它 bean,那麼我們將進入接下來的流程。

5) 不同作用域(Scope) bean 的載入動作

如下的縮圖所示,就是本文剩下的全部內容了,這裡分為對:單例bean、原型(「多例」)bean、其它bean的載入,實際上我們接觸最多的就是單例bean的載入。

其實這裡雖然根據不同作用域分為了3種情形,實際進行bean載入的動作其實大同小異,主要的差異在體現在作用域的特性上:

  • 單例:它關注的bean快取的操作,保證全域性唯一
  • 原型(多例): 它不需要保證唯一,每次直接建立全新bean,所以 spring 中對多例 bean的載入邏輯,反而比單例bean的載入簡單了太多太多。

6) 深入瞭解 doGetBean() 之 《如何從零開始建立bean》

本文篇幅將會拉長,故通過單獨的文章呈現。

6、從零開始的 bean 建立.md

7) 深入瞭解 doGetBean() 之: 從零開始的單例bean建立

8、單例bean的建立

8) 深入瞭解 doGetBean() 之 原型(多例) bean 的載入

從零開始的 多例bean 建立沒啥好說的,因為每次必定建立全新的bean,那麼就不需要設定快取、全域性唯一性檢查等操作了,
所以多例 bean的 建立邏輯反而就只剩下了:

  • createBean() + getObjectForBeanInstance()

9) 深入瞭解 doGetBean() 之: 其它作用域的bean建立

看這個程式碼結構,是不是很眼熟?跟單例bean的建立程式碼大同小異。

那麼我們可以推測,當需要使用其它作用域時,那麼需要向 BeanFactory 註冊相關 "作用域的解析器",也是上述截圖中的:

  • scopes

對應作用域解析器,根據作用特性開發滿足條件的 Scope.get(String beanName, ObjectFactory objFactory) 即可。

比如我們前邊提到的單例作用域bean,就一個目的: 全域性唯一。

如果忘了 bean 還有哪些作用域,可以回頭去複習一下。

10) 深入瞭解 doGetBean() 之 生成的 bean型別檢查

這裡也很簡單,getBean 支援指定 bean的型別,當bean 的載入結束後,如果引數指定了: requiredType

那麼就需要進行引數型別校驗。

5 系列二 getBean() 總結

這裡,除了單純的bean範例化、初始化,還包含了很多其它的的知識:

  • spring bean 的作用域及其特性,以及spring 面臨相關作用域bean的載入時所做的動作;

  • FactoryBean 和 ObjectFactory

  • 迴圈依賴

  • 後置處理器

... 等等

別看僅一個 getBean() , 包含的東西實在太多太多了。

6 後續展望

實際上到了這裡我們對 spring 基礎功能的學習還是有所欠缺,比如大名鼎鼎的:AOP

畢竟 IOC 和 AOP 是 spring 的兩大基本特性麼。

不過好訊息是對於AOP 的學習可以繼承到目前為止我們的學習成果。

  • 實際上簡單來說 AOP 乾的事,就是普通bean 被載入成功後,再應用動態代理技術生成bean的子類,
    並把通過AOP定義增強內容實現。