《系列二》-- 6、從零開始的 bean 創建

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

寫在開始前的話:

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

  • beans
  • core
  • context

實際上我在博客裏貼出來的還只是一部分內容,更多的內容,我放在了個人,fork自 spring 官方源碼倉了; 而且對源碼的學習,必須是要跟着實際代碼層層遞進的,不然只是乾巴巴的文字味同嚼蠟。

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

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



Spring源碼閱讀系列--全局目錄.md



createBean() 的面紗

我們在 AbstractBeanFactory 裏找到的 createBean() 只是個 抽象方法,如果使用 IDEA 的朋友,可以:

  • Ctrl + Alt + 鼠標左鍵

最終在它的子類 AbstractAutowireCapableBeanFactory 中找到的 createBean() 方法的實現

  • String beanName: bean名稱
  • RootBeanDefinition mbd: bean配置元數據
  • @Nullable Object[] args: 不認識的一律當作空的處理,它可能只在某些特殊場景下被使用

下邊是官方源碼 + 個人添加的註釋:

/**
 * Central method of this class: creates a bean instance,
 * populates the bean instance, applies post-processors, etc.
 * @see #doCreateBean
 */
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    if (logger.isDebugEnabled()) {
        logger.debug("Creating instance of bean '" + beanName + "'");
    }
    // 解析過程實際用到的 bean元數據
    RootBeanDefinition mbdToUse = mbd;

    // Make sure bean class is actually resolved at this point, and
    // clone the bean definition in case of a dynamically resolved Class
    // which cannot be stored in the shared merged bean definition.
    // 籠統的說:根據bean元數據(BeanDefinition) 獲取 bean 的class
    // 1 若元數據中包含了 類class對象,直接返回。
    // 2 否則獲取線程的 ClassLoader 根據 bean的 ClassName 得到“類的Class對象”,這個解析過程同樣涉及到了緩存,
    // 		這個 “類的Class”對象,會被當作緩存保存到 bean的元數據(BeanDefinition 中),
    // 		也就是第一不提到的情形,實際上可能就是 “多例”bean 加載是在重複利用 bean元數據。
    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
        // 套娃驗證 + 類緩存機制; spring 代碼的健壯性可見一斑
        mbdToUse = new RootBeanDefinition(mbd);
        mbdToUse.setBeanClass(resolvedClass);
    }

    // Prepare method overrides.
    try {
        // replace-method / lookup-method 的有效性校驗 <若存在>
        mbdToUse.prepareMethodOverrides();
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                beanName, "Validation of method overrides failed", ex);
    }

    // 實例化 | 初始化
    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        //  調用 "實例化前 - 後置處理器", 解析指定 bean 是否存在初始化前的短路操作
        // 因爲我們並沒有往 XmlBeanFactory 配置,代表任何行爲的 "後置處理器" 所以這一步可以跳過
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            // 短路處理,如果:實例化-前置處理, 返回結果不爲空,直接將其作爲處理結果返回,不再執行: doCreateBean(....)
            return bean;
        }
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                "BeanPostProcessor before instantiation of bean failed", ex);
    }

    try {
        // 見到 doCreateBean() 代表進入正文了,前邊講循環依賴,講單例bean時,提到了"三級緩存",其中優先級最低的 "第三級" 緩存就是從
        // doCreateBean() 方法中注入的
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);  // 創建 bean 
        if (logger.isDebugEnabled()) {
            logger.debug("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        // A previously detected exception with proper bean creation context already,
        // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
                mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
    }
}

上邊的代碼有提到一個概念:實例化前 - 後置處理器,若不瞭解相關概念可參見下文

[7、後置處理器-PostProcessor] (7、後置處理器-PostProcessor.md)

本節的介紹止於: doCreateBean()

接下來我們繼續......

4 createBean() 的承包者: doCreateBean()

它除了是 createBean() 的包工頭之外,它還是 "三級緩存" 機制的起點;

"三級緩存" 和 "循環依賴的消解" 也是個大活呢。

老規矩,同樣是代碼加 + 註釋的方式展開:

/**
 * Actually create the specified bean. Pre-creation processing has already happened
 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
 * <p>Differentiates between default bean instantiation, use of a
 * factory method, and autowiring a constructor.
 *
 * @param beanName the name of the bean
 * @param mbd      the merged bean definition for the bean
 * @param args     explicit arguments to use for constructor or factory method invocation
 * @return a new instance of the bean
 * @throws BeanCreationException if the bean could not be created
 * @see #instantiateBean
 * @see #instantiateUsingFactoryMethod
 * @see #autowireConstructor
 */
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        // ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache

        // 1 併發場景下起手式: 單例bean先嚐試撈緩存
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);

    }
    if (instanceWrapper == null) {

        // 2 緩存中沒撈到?唔好意思,只能從頭開始解析了。根據 beanName 使用對應策略創建 bean, 例如:工廠方法、構造函數自動注入
        // 		簡單來說就是:bean元數據(BeanDefinition),合法性校驗: 匹配參數、構造函數/factory-bean, 應用BeanFactory
        // 		的實例化策略, 實例化指定bean

        // 返回的bean 是由 Cglib 動態代理生成的,可以參考包裝器模式去理解它
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // 從包裝器中獲取 bean 實例
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        // 這裏對 bean元數據屬性的設置,八九不離十是緩存 (我們在前邊搞不好已經見過它也說不定)
        mbd.resolvedTargetType = beanType;
    }

    // MergedBeanDefinitionPostProcessor 應用
    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) { // 對 BeanDefinition 對象上鎖
        if (!mbd.postProcessed) { // 雙重鎖機制,避免重複
            try {
                // PostProcessor ? 那麼明顯是個 "後置處理器",不妨從命名猜測下:
                // MergedBeanDefinition ? 走到這一步我們已經獲取到了bean的實例,那麼八九不離十,這裏是根據bean的解析結果對
                // mbd(bean元數據) 進行類似緩存設置相關的操作、或者進行一些檢查性質類似的操作
                // 接下來我們進去一探究竟
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            } catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            // 雙重鎖釋放
            mbd.postProcessed = true;
        }
    }

    // 4 依賴處理
    // 這裏做了一件事:利用條件進行判斷,當前指定加載的bean是否允許提早曝光

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    // 淺淺的按個人的理解翻譯下這句話:急切的進行單例緩存,即使在bean的較早的生命週期環節,也允許被循環依賴。
    // 這裏提到了生命週期,那麼可能是在警示 allowCircularReferences 的使用 ,如果開啓它,可能導致 bean 在不完全加載時被循環依賴

    // 條件: bean是單例 && 允許循環依賴 && bean正在創建    >>>   結果: 是否允許提早曝光
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 三級緩存:它不就來了麼? addSingletonFactory方法操作的: singletonFactories 難道不是老熟人麼?
        // 如果已經忘了可以再去看看,本系列第五篇:單例bean緩存的獲取

        // 提早曝光: 初始化完成前,將創建包含bean實例的 ObjectFactory對象提早曝光加入工廠
        // -> (beanName,new ObjectFactory())
        addSingletonFactory(beanName, () -> {
            // 應用 InstantiationAwareBeanPostProcessor,在此過程中涉及將AOP的advice,動態織入bean,若無AOP配置則直接返回bean
            // 記得全局搜索這個關鍵詞: (SmartInstantiationAwareBeanPostProcessor) 不止提前暴露bean的時候執行這些 "後置處理器"
            // 我們定會重逢的

            // 此處,當三級緩存被獲取時,ObjectFactory().getObject() 執行的內容,最終將由如下的方法承包
            return getEarlyBeanReference(beanName, mbd, bean);
        });
    }

    // Initialize the bean instance. 【初始化:實例化得到 bean 對象,然後通過初始化操作,進行bean屬性的填充。】
    Object exposedObject = bean;
    try {
        // 雖然:bean = instanceWrapper.getWrappedInstance(); 但是需要注意的是,如果bean 被提前暴露了,這裏的 bean 可能是被:
        // 後置處理器: SmartInstantiationAwareBeanPostProcessor 加工之後的結果
        // 這取決於 SmartInstantiationAwareBeanPostProcessor 返回的結果是否還是原始的bean對象。 (記錄爲保留問題)

        // 5  屬性填充 bean (初始化)  將依賴屬性注入,如果引用別的 bean 則遞歸的去進行 bean 的 初始化
        // 方法名直譯就是:填充bean,這裏的操作基本上,就是從 bean 的元數據 mdb上讀取相關屬性,並設置到新的 bean 上
         populateBean(beanName, mbd, instanceWrapper);

        // 調用 bean.xml中定義的初始化方法,例如: method-init 等等
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        } else {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    // 6  循環依賴 檢查
    // 檢查已經加載的bean  是否存在 非 setter 循環依賴
    // 單例循環依賴檢查  可通過配置消解
    if (earlySingletonExposure) { // 如果當前bean 被提前暴露
        // 應用三級緩存,讀取被提前暴露的 bean
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 緩存讀取結果不爲空,那麼說明存在循環依賴
            if (exposedObject == bean) {
                // 這裏的判斷結果表明: 處理循環依賴時提前暴露的bean 跟  被一系列後置處理器加工過後的bean 的 "對象堆內存地址一致"
                // 說明 "後置處理器" 的加工,沒有導致原始 bean 對象,沒有被替換
                exposedObject = earlySingletonReference;
            }
            // allowRawInjectionDespiteWrapping :  在循環引用的情況下,是否需要注入原始bean實例,即使注入的bean最終被包裝。
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                // 對象地址不等,說明加工過程中,原始的 bean 對象,已經被偷樑換柱了

                // 獲取當前指定bean 依賴的別的bean名稱
                String[] dependentBeans = getDependentBeans(beanName);

                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    // 依賴檢測
                    // dependentBeans 包含的時當前bean 所依賴其它bean
                    // 1. removeSingletonIfCreatedForTypeCheckOnly = true 表示當前 bean,循環依賴了別的尚處於創建的中的 bean
                    // <等同提前暴露>

                    // 2. removeSingletonIfCreatedForTypeCheckOnly = false, 表示依賴結構中無環,當前指定bean依賴的其它bean
                    // 都已經被完全成功的加載
                    
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                // actualDependentBeans 爲空說明依賴不成環,或者依賴的bean 全部都已經被加載成功
                // actualDependentBeans 不爲空,說明有依賴的bean 未完全加載,那麼必定存在循環依賴
                if (!actualDependentBeans.isEmpty()) {
                    // 當前指定bean 存在循環依賴,依賴的其它bean未加載完全
                    
                    // allowRawInjectionDespiteWrapping 爲 false 時:
                    // 		若原始bean對象被後置處理器替換 && 原始bean 已經被當作循環依賴注入了別的bean中
                    // 那麼拋出異常,bean 加載失敗
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                                    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                    "] in its raw version as part of a circular reference, but has eventually been " +
                                    "wrapped. This means that said other beans do not use the final version of the " +
                                    "bean. This is often the result of over-eager type matching - consider using " +
                                    "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        // 如果配置了 destroy-method 這裏需要註冊,以保證對象實例銷燬時調用
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    } catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

別看只有這一個方法,但是裏面包含的東西太多太多了:

  1. bean 實例化,默認實例化策略: Cglib <動態代理>
  • 根據 factory-method 或者 factory-bean實例化; 若如相關配置,則配置的應用構造函數,實例化bean;
    若沒有配置,構造函數,則應用無參構造函數實例化bean。

雖然這裏只有輕飄飄的一句話: 實例化,但是實際操作時卻包羅萬象; 比如:
a. 應用緩存策略,先確定該 bean 是否被解析過;如果則進入如下的流程;
b. 根據參數個數從 元數據(BeanDefinition) 中匹配: factory-method / 構造函數
c. 參數 [ 類型轉換 / 值解析 ]

  1. bean 元數據解析後,如若是第一次被解析,將解析的中間結果,以緩存的形式,在 (BeanDefinition) 中保存

  2. 單例bean: 判斷是否允許循環依賴, 如若允許提前暴露,將其置入三級緩存

  3. 屬性填充、應用: "實例化後置處理器"

  4. 初始化方法應用: bean.xml 配置的 init-method

  5. 如果 bean 已經被提前暴露,那麼判斷:三級緩存中的bean對象 和 被後置處理器加工之後的bean對象,
    堆內存地址是否一致:

    • 如若不一致,還需要應用BeanFactory的配置:allowRawInjectionDespiteWrapping,該配置決定:
      • "實例化後置處理器" 替換了原始bean時,是否允許循環依賴
  6. 如果元數據 bean.xml 中配置了,destroy-method,那麼需要同樣需要註冊該方法到 bean中
    以確保bean的生命週期結束後,能應用正確的銷燬動作

總結

本文到此爲止,幾乎已經講完了,如何從零開始創建一個 bean;由於篇幅問題,更多細節並未在上述文中完全得到體現。

如下是本人一邊閱讀源碼,一邊做筆記的個人 spring 代碼倉;如若想要了解更多細節,不妨拉取下方源碼,一探究竟。

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

  • 聲明:代碼中的註釋僅僅代表個人觀點,該代碼倉也僅用作個人學習所用,如有謬誤感謝指正。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章