Spring解析之IoC:bean的加載(二)

前言
上一篇bean加載的文章分析了bean加載核心入口AbstractBeanFactory#doGetBean(String, Class, Object[], boolean)的上半部分,該部分主要邏輯在顯式調(diào)用getBean時(shí)會(huì)被執(zhí)行,更具體的來(lái)說(shuō)是獲得單例對(duì)象和通過(guò)自定義FactoryBean創(chuàng)建對(duì)象時(shí)會(huì)執(zhí)行上半部分;而下半部分會(huì)在初始化和獲得prototype多例對(duì)象時(shí)被執(zhí)行,本文就是對(duì)這下半部分做深入分析

為了分析方便,我們?cè)俳厝∫淮蜗掳氩糠謨?nèi)容的代碼,代碼清單1

        //    (1)
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }

    //    (2)
    BeanFactory parentBeanFactory = getParentBeanFactory();
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
        // Not found -> check parent.
        String nameToLookup = originalBeanName(name);
        if (args != null) {
            // Delegation to parent with explicit args.
            return (T) parentBeanFactory.getBean(nameToLookup, args);
        }
        else {
            // No args -> delegate to standard getBean method.
            return parentBeanFactory.getBean(nameToLookup, requiredType);
        }
    }
        //    (3)
    if (!typeCheckOnly) {
        markBeanAsCreated(beanName);
    }

    try {
                //    (4)
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        checkMergedBeanDefinition(mbd, beanName, args);

        //    (5)
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
            for (String dependsOnBean : dependsOn) {
                getBean(dependsOnBean);
                registerDependentBean(dependsOnBean, beanName);
            }
        }
                //    (6)
        // Create bean instance.
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                public Object getObject() throws BeansException {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                        // Explicitly remove instance from singleton cache: It might have been put there
                        // eagerly by the creation process, to allow for circular reference resolution.
                        // Also remove any beans that received a temporary reference to the bean.
                        destroySingleton(beanName);
                        throw ex;
                    }
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
                //    (7)
        else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
                beforePrototypeCreation(beanName);
                prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
                afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        }
                //    (8)
        else {
            String scopeName = mbd.getScope();
            final Scope scope = this.scopes.get(scopeName);
            if (scope == null) {
                throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
            }
            try {
                Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                    }
                });
                bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
            catch (IllegalStateException ex) {
                throw new BeanCreationException(beanName,
                        "Scope '" + scopeName + "' is not active for the current thread; " +
                        "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                        ex);
            }
        }
    }
    catch (BeansException ex) {
        cleanupAfterBeanCreationFailure(beanName);
        throw ex;
    }

我們都知道對(duì)于scope = "prototype"的對(duì)象來(lái)說(shuō),Spring是不會(huì)在真正使用該對(duì)象前創(chuàng)建它的,這一特性意味著Spring不能解決prototype類型變量的循環(huán)依賴問(wèn)題,道理很簡(jiǎn)單,當(dāng)我們真正使用prototype對(duì)象時(shí)肯定要求內(nèi)部的依賴關(guān)系都建立完畢,而循環(huán)依賴的存在使這種對(duì)象間關(guān)系無(wú)法建立,必定就會(huì)拋出BeanCurrentlyInCreationException
上一篇文中說(shuō)過(guò),Spring使用“三級(jí)緩存”的形式能部分解決循環(huán)依賴問(wèn)題,我們?cè)谶@里就可以總結(jié)下“部分”指哪些情況:Spring能夠解決singleton下使用setter方式形成的循環(huán)依賴問(wèn)題,不能夠解決singleton下使用構(gòu)造器形成的循環(huán)依賴,以及prototype下的循環(huán)依賴。第一種能解決的原因是Spring解析之IoC:bean的加載(一)中分析過(guò)的緩存earlySingletonObjects,當(dāng)我們?cè)O(shè)置允許早期對(duì)象引用暴露allowEarlyReference后,Spring會(huì)將通過(guò)構(gòu)造器創(chuàng)建出來(lái)的,尚未調(diào)用setter建立依賴關(guān)系的單例對(duì)象放入earlySingletonObjects中,當(dāng)我們需要一組存在循環(huán)依賴的對(duì)象時(shí)直接從里面取出每個(gè)對(duì)象手動(dòng)建立依賴關(guān)系即可。但是如果是構(gòu)造器形成的循環(huán)依賴,連創(chuàng)建早期對(duì)象都不可能,自然就沒(méi)法解決循環(huán)依賴問(wèn)題了
重溫了Spring解決循環(huán)依賴的手段后看標(biāo)注1,如果獲取的是prototype類型的對(duì)象且該對(duì)象正在創(chuàng)建中,必然就發(fā)生了循環(huán)依賴,直接拋出異常。標(biāo)注2是存在父子容器時(shí)加載bean的代碼邏輯,如果大家用過(guò)SpringMVC相信對(duì)父子容器都不陌生,大多數(shù)情況下Web層的SpringMVC都作為Service層和Dao層Spring容器的子容器存在,子容器可以訪問(wèn)父容器中的bean,反過(guò)來(lái)則不行,當(dāng)子容器加載bean時(shí)首先判斷自身容器中是否存在同名的bean,存在直接獲得,不存在就去父容器中查找該名稱的bean
標(biāo)注2的邏輯就是這樣,判斷如果存在父容器,且當(dāng)前容器中不存在beanName對(duì)應(yīng)的bean就調(diào)用父容器parentBeanFactorygetBean(String, Object...),又來(lái)了一個(gè)大循環(huán)。標(biāo)注3中typeCheckOnly表示獲得bean的目的是否是為了類型檢查,而不是真正使用這個(gè)bean,絕大部分情況我們當(dāng)時(shí)是要用bean啦,這時(shí)就要調(diào)用markBeanAsCreated(String)將創(chuàng)建bean的行為做記錄,記錄實(shí)際上就是將beanName打上已經(jīng)創(chuàng)建的標(biāo)識(shí)放入Map<String, Boolean> alreadyCreated
標(biāo)注4在Spring解析之IoC:bean的加載(一)中已經(jīng)說(shuō)過(guò),如果<bean>存在父子關(guān)系(注意不是容器的父子關(guān)系),getMergedLocalBeanDefinition(String)會(huì)將父子關(guān)系的<bean>信息融合在RootBeanDefinition

圖1. 檢查融合后的BeanDefinition

checkMergedBeanDefinition(RootBeanDefinition, String, Object[])排除了兩種情況下bean的創(chuàng)建:1. <bean>存在abstract屬性;2. scope = singleton且參數(shù)args有值的情況。第一種情況很好理解,都抽象了還創(chuàng)建毛線啊,而要想通第二種情況我們需要追根溯源看看args來(lái)自哪里。顯式調(diào)用getBean獲得對(duì)象有一個(gè)重載方法Object getBean(String, Object...),這里的args就是第二個(gè)參數(shù),該重載方法提供的目的是解決一個(gè)場(chǎng)景:初始化只存在有參構(gòu)造的類,且參數(shù)要在獲取時(shí)動(dòng)態(tài)指定。創(chuàng)建只存在有參構(gòu)造器的類很容易,直接<constrcut-arg>指定就好,但是要每次創(chuàng)建的參數(shù)值不同再用該方法很明顯就掛了啊,還是Object getBean(String, Object...)好使。這也很好的解釋了為什么args不能和singleton共存,因?yàn)?code>args說(shuō)明可變性,singleton說(shuō)明唯一性,相互沖突
標(biāo)注5涉及到一個(gè)之前沒(méi)有講過(guò)的<bean>屬性depends-on,該屬性的作用是讓depends-on內(nèi)的對(duì)象先于<bean>所代表的對(duì)象創(chuàng)建,但這兩組對(duì)象并不要求有真正的依賴關(guān)系,我們舉個(gè)例子,創(chuàng)建三個(gè)類Man、WomanFamilyFamily配置depends-on前兩個(gè)類

圖2. Man

圖3. Woman

圖4. Family

注意FamilyMan、Woman只是組合關(guān)系并沒(méi)有形成依賴,在XML進(jìn)行配置如下
圖5. Man、Woman和Family相關(guān)配置

如果depends-on多個(gè)對(duì)象,多個(gè)對(duì)象之間可以用,隔開(kāi),運(yùn)行結(jié)果如下
圖6. depends-on例子運(yùn)行結(jié)果

很明顯Spring先初始化了depends-on的對(duì)象,至于多個(gè)depends-on對(duì)象創(chuàng)建之間的順序和XML中對(duì)應(yīng)<bean>書(shū)寫(xiě)順序有關(guān)和depends-on中的順序無(wú)關(guān),其實(shí)就是Spring自上而下解析標(biāo)簽的順序。讓我們?cè)倩氐?strong>代碼清單1,標(biāo)注6、7、8三處很明顯根據(jù)scope的不同將處理邏輯分成了三塊,為了分析清楚我們將每一塊單獨(dú)拎出來(lái)
圖7. 處理singleton對(duì)象邏輯

scope = singleton對(duì)象的處理也分為三部分,1、2兩部分是互有關(guān)聯(lián)的,Object getSingleton(String, ObjectFactory)第二個(gè)參數(shù)是接口ObjectFactory的匿名實(shí)現(xiàn),實(shí)現(xiàn)了Object getObject()方法,具體的實(shí)現(xiàn)又調(diào)用了Object createBean(String, RootBeanDefinition, Object[]),而該方法又是一個(gè)模板,真正的具體實(shí)現(xiàn)在AbstractAutowireCapableBeanFactory中,我們先走進(jìn)標(biāo)注1看看做了什么
圖8. DefaultSingletonBeanRegistry的getSingleton(String, ObjectFactory<?>)

首先從緩存singletonObejcts中獲取該單例對(duì)象,不存在進(jìn)入創(chuàng)建流程,beforeSingletonCreation(String)做創(chuàng)建對(duì)象前的處理工作,之后調(diào)用匿名實(shí)現(xiàn)的getObject()進(jìn)而調(diào)用上面說(shuō)的模板方法createBean創(chuàng)建對(duì)象,afterSingletonCreate(String)做一些后處理操作,最后addSingleton(String, Object)將創(chuàng)建的單例對(duì)象放入緩存
圖9. DefaultSingletonBeanRegistry的beforeSingletonCreation(String)

isCreationCheckExclusions保存在創(chuàng)建時(shí)不需要做校驗(yàn)的bean名稱,singletonCurrentlyInCreation大家應(yīng)該很熟悉了,保存正在創(chuàng)建過(guò)程中的對(duì)象,整體邏輯就是,如果對(duì)象在創(chuàng)建時(shí)需要做校驗(yàn)(說(shuō)明還沒(méi)真正創(chuàng)建),但在正在創(chuàng)建對(duì)象的容器中又有它,那就說(shuō)明有問(wèn)題,拋出BeanCurrentlyInCreationException。同時(shí)這一步也讓大家知道了用于檢測(cè)循環(huán)依賴的singletonCurrentlyInCreation是什么時(shí)候被塞入內(nèi)容的
分析了這么多還在外圍轉(zhuǎn)悠,下面的singletonFactory.getObject()是創(chuàng)建bean的核心代碼了吧?是也不是,是是因?yàn)楹诵膭?chuàng)建流程確實(shí)在該方法中,不是是因?yàn)樾⌒〉姆椒ɡ锩嫔婕暗臇|東那多的啊,Spring的東西果然浩瀚如海啊。正因?yàn)檫@個(gè)問(wèn)題的存在我想還是將核心邏輯再開(kāi)一篇文章單獨(dú)分析吧,要不然這篇文章得寫(xiě)多少啊,讀者傷心寫(xiě)者流淚啊。我們現(xiàn)在只需知道singletonFactory.getObject()主要得到的對(duì)象就兩種:1.和<bean>對(duì)應(yīng)的真實(shí)bean;2.創(chuàng)建bean的自定義FactoryBean實(shí)例,本篇文章先將外圍邏輯都清理干凈
afterSingletonCreation(String)閉著眼睛想都知道是創(chuàng)建單例之后的處理邏輯,和beforeSingletonCreation(String)唯一不同的在于,后者是將正在創(chuàng)建的對(duì)象放入singletonsCurrentlyInCreation,而前者是創(chuàng)建完對(duì)象后從singletonsCurrentlyInCreation移除。addSingleton(String, Object)邏輯也很簡(jiǎn)單
圖10. DefaultSingletonBeanRegistry的addSingleton(String, Object)

Spring解析之IoC:bean的加載(一)中提到部分解決循環(huán)依賴的“三級(jí)緩存”,也說(shuō)到過(guò)數(shù)據(jù)只能存在其中一個(gè)緩存中,這里就是當(dāng)對(duì)象創(chuàng)建完成后將對(duì)象放入“一級(jí)”緩存中并刪除其余緩存中的該對(duì)象流程,并在registeredSingletons已注冊(cè)對(duì)象HashSet中保存對(duì)應(yīng)的beanName
回到圖7,標(biāo)注1、2都分析過(guò)了,標(biāo)注3更好說(shuō)了,同樣在Spring解析之IoC:bean的加載(一)中已經(jīng)進(jìn)行了詳細(xì)的分析,如果生成對(duì)象為bean直接返回,如果是自定義FactoryBean,調(diào)用其實(shí)現(xiàn)的getObject()創(chuàng)建bean后返回,單例創(chuàng)建對(duì)象分析完畢,開(kāi)始多例對(duì)象創(chuàng)建分析
圖11. 處理prototype對(duì)象邏輯

從宏觀上看scope = prototype處理流程和單例時(shí)一樣,圍繞createBean(String, RootBeanDefinition, Object[])進(jìn)行前后校驗(yàn)處理,最后將可能的FactoryBean轉(zhuǎn)成特定的bean返回
圖12. AbstractBeanFactory的beforePrototypeCreation(String)

prototypesCurrentlyInCreation是一個(gè)ThreadLocal<Object>變量,其中保存了當(dāng)前線程正在創(chuàng)建的所有多例對(duì)象,只存在一個(gè)多例對(duì)象時(shí)ThreadLocal內(nèi)存的就是字符串beanName,當(dāng)有多個(gè)時(shí)內(nèi)部存儲(chǔ)的就是HashSet集合,這里的存儲(chǔ)又和本文最開(kāi)始用prototypesCurrentlyInCreation做多例類型循環(huán)依賴的判斷對(duì)應(yīng)上了。createBean(String, RootBeanDefinition, Object[])和上面一樣暫時(shí)跳過(guò),afterPrototypeCreation(String)思路和單例的后處理相似,將創(chuàng)建好的多例對(duì)象從prototypesCurrentlyInCreation中移除,最后一步getObjectForBeanInstance(Object, String, String, RootBeanDefinition)和之前分析的一模一樣,不再贅述。最后一組是剩下所有scope對(duì)象的處理邏輯,除了我們最常用的singletonprototype外,針對(duì)Web項(xiàng)目Spring又提供了request、session、global session等其他類型(不同Spring版本scope也不一樣,到時(shí)大家看到多幾個(gè)少幾個(gè)不用詫異),此外我們還可以實(shí)現(xiàn)Scope接口創(chuàng)建自定義的scope,這里給一篇文章上面有Spring3.0相關(guān)scope類型的用法講解,大家可以拿來(lái)耍耍Bean scopes
圖13. 處理其他scope對(duì)象邏輯

其實(shí)其他scope類型創(chuàng)建bean的邏輯從上圖看和scope = “prototype”相似,先從RootBeanDefinition中得到配置的scope,然后從Map<String, Scope> scopes中得到該scope對(duì)應(yīng)的處理類,根據(jù)處理類中實(shí)現(xiàn)的Object get(String, ObjectFactory<?>),如果實(shí)現(xiàn)的邏輯中調(diào)用了ObjectFactoryObject getObject(),那就又回到了多例的處理邏輯,剩下的大家看前面的分析即可

后記
本文將getBean中最后零碎的邏輯清理干凈就是為了將核心創(chuàng)建bean作為一個(gè)整體分析,即便如此由于Spring涉及的內(nèi)容太多,createBean依然比較龐大和雜亂,不管怎么說(shuō)我們已經(jīng)吹響了最后的沖鋒號(hào),年前必定攻下獲得bean這座山頭,加油!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容