1. 程式人生 > >Spring IOC 容器源碼分析 - 創建單例 bean 的過程

Spring IOC 容器源碼分析 - 創建單例 bean 的過程

event version trac 方法 del lB ctu prepare 先來

1. 簡介

在上一篇文章中,我比較詳細的分析了獲取 bean 的方法,也就是getBean(String)的實現邏輯。對於已實例化好的單例 bean,getBean(String) 方法並不會再一次去創建,而是從緩存中獲取。如果某個 bean 還未實例化,這個時候就無法命中緩存。此時,就要根據 bean 的配置信息去創建這個 bean 了。相較於getBean(String)方法的實現邏輯,創建 bean 的方法createBean(String, RootBeanDefinition, Object[])及其所調用的方法邏輯上更為復雜一些。關於創建 bean 實例的過程,我將會分幾篇文章進行分析。本篇文章會先從大體上分析 createBean(String, RootBeanDefinition, Object[])

方法的代碼邏輯,至於其所調用的方法將會在隨後的文章中進行分析。

好了,其他的不多說,直接進入正題吧。

2. 源碼分析

2.1 創建 bean 實例的入口

在正式分析createBean(String, RootBeanDefinition, Object[])方法前,我們先來看看 createBean 方法是在哪裏被調用的。如下:

public T doGetBean(...) {
    // 省略不相關代碼
    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
            @Override
public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); bean = getObjectForBeanInstance
(sharedInstance, name, beanName, mbd); } // 省略不相關代碼 }

上面是 doGetBean 方法的代碼片段,從中可以發現 createBean 方法。createBean 方法被匿名工廠類的 getObject 方法包裹,但這個匿名工廠類對象並未直接調用 getObject 方法。而是將自身作為參數傳給了getSingleton(String, ObjectFactory)方法,那麽我們接下來再去看看一下getSingleton(String, ObjectFactory) 方法的實現。如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "‘beanName‘ must not be null");
    synchronized (this.singletonObjects) {
        // 從緩存中獲取單例 bean,若不為空,則直接返回,不用再初始化
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName,
                        "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                        "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Creating shared instance of singleton bean ‘" + beanName + "‘");
            }
            /* 
             * 將 beanName 添加到 singletonsCurrentlyInCreation 集合中,
             * 用於表明 beanName 對應的 bean 正在創建中
             */
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<Exception>();
            }
            try {
                // 通過 getObject 方法調用 createBean 方法創建 bean 實例
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 將 beanName 從 singletonsCurrentlyInCreation 移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                /* 
                 * 將 <beanName, singletonObject> 鍵值對添加到 singletonObjects 集合中,
                 * 並從其他集合(比如 earlySingletonObjects)中移除 singletonObject 記錄
                 */
                addSingleton(beanName, singletonObject);
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

上面的方法邏輯不是很復雜,這裏簡單總結一下。如下:

  1. 先從 singletonObjects 集合獲取 bean 實例,若不為空,則直接返回
  2. 若為空,進入創建 bean 實例階段。先將 beanName 添加到 singletonsCurrentlyInCreation
  3. 通過 getObject 方法調用 createBean 方法創建 bean 實例
  4. 將 beanName 從 singletonsCurrentlyInCreation 集合中移除

從上面的分析中,我們知道了 createBean 方法在何處被調用的。那麽接下來我們一起深入 createBean 方法的源碼中,來看看這個方法具體都做了什麽事情。

2.2 createBean 方法全貌

createBean 和 getBean 方法類似,基本上都是空殼方法,只不過 createBean 的邏輯稍微多點,多做了一些事情。下面我們一起看看這個方法的實現邏輯,如下:

protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating instance of bean ‘" + beanName + "‘");
    }
    RootBeanDefinition mbdToUse = mbd;

    // 解析 bean 的類型
    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
        mbdToUse = new RootBeanDefinition(mbd);
        mbdToUse.setBeanClass(resolvedClass);
    }

    try {
        // 處理 lookup-method 和 replace-method 配置,Spring 將這兩個配置統稱為 override method
        mbdToUse.prepareMethodOverrides();
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                beanName, "Validation of method overrides failed", ex);
    }

    try {
        // 在 bean 初始化前應用後置處理,如果後置處理返回的 bean 不為空,則直接返回
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                "BeanPostProcessor before instantiation of bean failed", ex);
    }

    // 調用 doCreateBean 創建 bean
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    if (logger.isDebugEnabled()) {
        logger.debug("Finished creating instance of bean ‘" + beanName + "‘");
    }
    return beanInstance;
}

上面的代碼不長,代碼的執行流程比較容易看出,這裏羅列一下:

  1. 解析 bean 類型
  2. 處理 lookup-method 和 replace-method 配置
  3. 在 bean 初始化前應用後置處理,若後置處理返回的 bean 不為空,則直接返回
  4. 若上一步後置處理返回的 bean 為空,則調用 doCreateBean 創建 bean 實例

下面我會分節對第2、3和4步的流程進行分析,步驟1的詳細實現大家有興趣的話,就自己去看看吧。

2.2.1 驗證和準備 override 方法

當用戶配置了 lookup-method 和 replace-method 時,Spring 需要對目標 bean 進行增強。在增強之前,需要做一些準備工作,也就是 prepareMethodOverrides 中的邏輯。下面來看看這個方法的源碼:

public void prepareMethodOverrides() throws BeanDefinitionValidationException {
    MethodOverrides methodOverrides = getMethodOverrides();
    if (!methodOverrides.isEmpty()) {
        Set<MethodOverride> overrides = methodOverrides.getOverrides();
        synchronized (overrides) {
            // 循環處理每個 MethodOverride 對象
            for (MethodOverride mo : overrides) {
                prepareMethodOverride(mo);
            }
        }
    }
}

protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
    // 獲取方法名為 mo.getMethodName() 的方法數量,當方法重載時,count 的值就會大於1
    int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
    // count = 0,表明根據方法名未找到相應的方法,此時拋出異常
    if (count == 0) {
        throw new BeanDefinitionValidationException(
                "Invalid method override: no method with name ‘" + mo.getMethodName() +
                "‘ on class [" + getBeanClassName() + "]");
    }
    // 若 count = 1,表明僅存在已方法名為 mo.getMethodName(),這意味著方法不存在重載
    else if (count == 1) {
        // 方法不存在重載,則將 overloaded 成員變量設為 false
        mo.setOverloaded(false);
    }
}

上面的源碼中,prepareMethodOverrides方法循環調用了prepareMethodOverride方法,並沒其他的太多邏輯。主要準備工作都是在 prepareMethodOverride 方法中進行的,所以我們重點關註一下這個方法。prepareMethodOverride 這個方法主要用於獲取指定方法的方法數量 count,並根據 count 的值進行相應的處理。count = 0 時,表明方法不存在,此時拋出異常。count = 1 時,設置 MethodOverride 對象的overloaded成員變量為 false。這樣做的目的在於,提前標註名稱mo.getMethodName()的方法不存在重載,在使用 CGLIB 增強階段就不需要進行校驗,直接找到某個方法進行增強即可。

上面的方法沒太多的邏輯,比較簡單,就先分析到這裏。

2.2.2 bean 實例化前的後置處理

後置處理是 Spring 的一個拓展點,用戶通過實現 BeanPostProcessor 接口,並將實現類配置到 Spring 的配置文件中(或者使用註解),即可在 bean 初始化前後進行自定義操作。關於後置處理較為詳細的說明,可以參考我的了一篇文章Spring IOC 容器源碼分析系列文章導讀,這裏就不贅述了。下面我們來看看 createBean 方法中的後置處理邏輯,如下:

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    // 檢測是否解析過,mbd.beforeInstantiationResolved 的值在下面的代碼中會被設置
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
        /* 
         * 關於 synthetic,簡單說一下我的理解,如下:
         * synthetic 字面意思是“合成的”,我覺得這個字段可能用於表示 mbd 對應的 bean 是否被 AOP 
         * 增強過,而 AOP 是通過後置處理增強 bean 的。所以如果 bean 被增強過,也就是 
         * synthetic = true。此時,不應該再次對 bean 進行增強。
         */
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            Class<?> targetType = determineTargetType(beanName, mbd);
            if (targetType != null) {
                // 應用前置處理
                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                if (bean != null) {
                    // 應用後置處理
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
        }
        // 設置 mbd.beforeInstantiationResolved
        mbd.beforeInstantiationResolved = (bean != null);
    }
    return bean;
}

protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        // InstantiationAwareBeanPostProcessor 一般在 Spring 框架內部使用,不建議用戶直接使用
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            // bean 初始化前置處理
            Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
            if (result != null) {
                return result;
            }
        }
    }
    return null;
}

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
        throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
       // bean 初始化後置處理
        result = beanProcessor.postProcessAfterInitialization(result, beanName);
        if (result == null) {
            return result;
        }
    }
    return result;
}

在 resolveBeforeInstantiation 方法中,當前置處理方法返回的 bean 不為空時,後置處理才會被執行。前置處理器是 InstantiationAwareBeanPostProcessor 類型的,該種類型的處理器一般用在 Spring 框架內部,比如 AOP 模塊中的AbstractAutoProxyCreator抽象類間接實現了這個接口中的 postProcessBeforeInstantiation方法,所以 AOP 可以在這個方法中生成為目標類的代理對象。不過我在調試的過程中,發現 AOP 在此處生成代理對象是有條件的。一般情況下條件都不成立,也就不會在此處生成代理對象。至於這個條件為什麽不成立,因 AOP 這一塊的源碼我還沒來得及看,所以暫時還無法解答。等我看過 AOP 模塊的源碼後,我再來嘗試分析這個條件。

2.2.3 調用 doCreateBean 方法創建 bean

這一節,我們來分析一下doCreateBean方法的源碼。在 Spring 中,做事情的方法基本上都是以do開頭的,doCreateBean 也不例外。那下面我們就來看看這個方法都做了哪些事情。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {

    /* 
     * BeanWrapper 是一個基礎接口,由接口名可看出這個接口的實現類用於包裹 bean 實例。
     * 通過 BeanWrapper 的實現類可以方便的設置/獲取 bean 實例的屬性
     */
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        // 從緩存中獲取 BeanWrapper,並清理相關記錄
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        /* 
         * 創建 bean 實例,並將實例包裹在 BeanWrapper 實現類對象中返回。createBeanInstance 
         * 中包含三種創建 bean 實例的方式:
         *   1. 通過工廠方法創建 bean 實例
         *   2. 通過有參構造方法方法創建 bean 實例
         *   3. 通過無參構造方法方法創建 bean 實例
          *
         * 若 bean 的配置信息中配置了 lookup-method 和 replace-method,則會使用 CGLIB 
         * 增強 bean 實例。關於這個方法,後面會專門寫一篇文章介紹,這裏先說這麽多。
         */
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // 此處的 bean 可以認為是一個原始的 bean 實例,暫未填充屬性
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
    Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
    mbd.resolvedTargetType = beanType;

    // 這裏又遇到後置處理了,此處的後置處理是用於處理已“合並的 BeanDefinition”。關於這種後置處理器具體的實現細節就不深入理解了,大家有興趣可以自己去看
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    /*
     * earlySingletonExposure 是一個重要的變量,這裏要說明一下。該變量用於表示是否提前暴露
     * 單例 bean,用於解決循環依賴。earlySingletonExposure 由三個條件綜合而成,如下:
     *   條件1:mbd.isSingleton() - 表示 bean 是否是單例類型
     *   條件2:allowCircularReferences - 是否允許循環依賴
     *   條件3:isSingletonCurrentlyInCreation(beanName) - 當前 bean 是否處於創建的狀態中
     * 
     * earlySingletonExposure = 條件1 && 條件2 && 條件3 
     *                        = 單例 && 是否允許循環依賴 && 是否存於創建狀態中。
     */
    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");
        }
        // 添加工廠對象到 singletonFactories 緩存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                // 獲取早期 bean 的引用,如果 bean 中的方法被 AOP 切點所匹配到,此時 AOP 相關邏輯會介入
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }

    Object exposedObject = bean;
    try {
        // 向 bean 實例中填充屬性,populateBean 方法也是一個很重要的方法,後面會專門寫文章分析
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            /*
             * 進行余下的初始化工作,詳細如下:
             * 1. 判斷 bean 是否實現了 BeanNameAware、BeanFactoryAware、
             *    BeanClassLoaderAware 等接口,並執行接口方法
             * 2. 應用 bean 初始化前置操作
             * 3. 如果 bean 實現了 InitializingBean 接口,則執行 afterPropertiesSet 
             *    方法。如果用戶配置了 init-method,則調用相關方法執行自定義初始化邏輯
             * 4. 應用 bean 初始化後置操作
             * 
             * 另外,AOP 相關邏輯也會在該方法中織入切面邏輯,此時的 exposedObject 就變成了
             * 一個代理對象了
             */
            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);
        }
    }

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 若 initializeBean 方法未改變 exposedObject 的引用,則此處的條件為 true。
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            // 下面的邏輯我也沒完全搞懂,就不分析了。見諒。
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    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 " +
                            "‘getBeanNamesOfType‘ with the ‘allowEagerInit‘ flag turned off, for example.");
                }
            }
        }
    }

    try {
        // 註冊銷毀邏輯
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

上面的註釋比較多,分析的應該比較詳細的。不過有一部分代碼我暫時沒看懂,就不分析了,見諒。下面我們來總結一下 doCreateBean 方法的執行流程吧,如下:

  1. 從緩存中獲取 BeanWrapper 實現類對象,並清理相關記錄
  2. 若未命中緩存,則創建 bean 實例,並將實例包裹在 BeanWrapper 實現類對象中返回
  3. 應用 MergedBeanDefinitionPostProcessor 後置處理器相關邏輯
  4. 根據條件決定是否提前暴露 bean 的早期引用(early reference),用於處理循環依賴問題
  5. 調用 populateBean 方法向 bean 實例中填充屬性
  6. 調用 initializeBean 方法完成余下的初始化工作
  7. 註冊銷毀邏輯

doCreateBean 方法的流程比較復雜,步驟略多。由此也可了解到創建一個 bean 還是很復雜的,這中間要做的事情繁多。比如填充屬性、對 BeanPostProcessor 拓展點提供支持等。以上的步驟對應的方法具體是怎樣實現的,本篇文章並不打算展開分析。在後續的文章中,我會單獨寫文章分析幾個邏輯比較復雜的步驟。有興趣的閱讀的朋友可以稍微等待一下,相關文章本周會陸續進行更新。

3. 總結

到這裏,createBean 方法及其所調用的方法的源碼就分析完了。總的來說,createBean 方法還是比較復雜的,需要多看幾遍才能理清一些頭緒。由於 createBean 方法比較復雜,對於以上的源碼分析,我並不能保證不出錯。如果有寫錯的地方,還請大家指點迷津。畢竟當局者迷,作為作者,我很難意識到哪裏寫的有問題。

好了,本篇文章到此結束。謝謝閱讀。

參考

  • 《Spring 源碼深度解析》- 郝佳

本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz

技術分享圖片
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。

Spring IOC 容器源碼分析 - 創建單例 bean 的過程