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

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

為了分析方便,我們再截取一次下半部分內(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;
    }

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

圖1. 檢查融合后的BeanDefinition

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

圖2. Man

圖3. Woman

圖4. Family

注意FamilyManWoman只是組合關(guān)系并沒有形成依賴搀矫,在XML進行配置如下
圖5. Man瓤球、Woman和Family相關(guān)配置

如果depends-on多個對象,多個對象之間可以用,隔開,運行結(jié)果如下
圖6. depends-on例子運行結(jié)果

很明顯Spring先初始化了depends-on的對象隅要,至于多個depends-on對象創(chuàng)建之間的順序和XML中對應(yīng)<bean>書寫順序有關(guān)和depends-on中的順序無關(guān),其實就是Spring自上而下解析標(biāo)簽的順序虏肾。讓我們再回到代碼清單1谴轮,標(biāo)注6第步、7、8三處很明顯根據(jù)scope的不同將處理邏輯分成了三塊翩隧,為了分析清楚我們將每一塊單獨拎出來
圖7. 處理singleton對象邏輯

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

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

isCreationCheckExclusions保存在創(chuàng)建時不需要做校驗的bean名稱迫摔,singletonCurrentlyInCreation大家應(yīng)該很熟悉了,保存正在創(chuàng)建過程中的對象纱烘,整體邏輯就是擂啥,如果對象在創(chuàng)建時需要做校驗(說明還沒真正創(chuàng)建)哺壶,但在正在創(chuàng)建對象的容器中又有它,那就說明有問題塌碌,拋出BeanCurrentlyInCreationException胖翰。同時這一步也讓大家知道了用于檢測循環(huán)依賴的singletonCurrentlyInCreation是什么時候被塞入內(nèi)容的
分析了這么多還在外圍轉(zhuǎn)悠萨咳,下面的singletonFactory.getObject()是創(chuàng)建bean的核心代碼了吧培他?是也不是,是是因為核心創(chuàng)建流程確實在該方法中猛遍,不是是因為小小的方法里面涉及的東東那多的啊梯醒,Spring的東西果然浩瀚如海啊茸习。正因為這個問題的存在我想還是將核心邏輯再開一篇文章單獨分析吧号胚,要不然這篇文章得寫多少啊,讀者傷心寫者流淚啊杜漠。我們現(xiàn)在只需知道singletonFactory.getObject()主要得到的對象就兩種:1.和<bean>對應(yīng)的真實bean驾茴;2.創(chuàng)建bean的自定義FactoryBean實例锈至,本篇文章先將外圍邏輯都清理干凈
afterSingletonCreation(String)閉著眼睛想都知道是創(chuàng)建單例之后的處理邏輯,和beforeSingletonCreation(String)唯一不同的在于们拙,后者是將正在創(chuàng)建的對象放入singletonsCurrentlyInCreation砚婆,而前者是創(chuàng)建完對象后從singletonsCurrentlyInCreation移除。addSingleton(String, Object)邏輯也很簡單
圖10. DefaultSingletonBeanRegistry的addSingleton(String, Object)

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

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

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

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秉馏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帆竹,更是在濱河造成了極大的恐慌栽连,老刑警劉巖升酣,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绩聘,居然都是意外死亡凿菩,警方通過查閱死者的電腦和手機衅谷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門蚀苛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堵未,“玉大人渗蟹,你說我怎么就攤上這事”嫠裕” “怎么了岛心?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诅诱。 經(jīng)常有香客問我干旁,道長争群,這世上最難降的妖魔是什么换薄? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮冲泥,結(jié)果婚禮上幸冻,老公的妹妹穿的比我還像新娘。我一直安慰自己革半,他們只是感情好又官,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驾荣,像睡著了一般审编。 火紅的嫁衣襯著肌膚如雪垒酬。 梳的紋絲不亂的頭發(fā)上勘究,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音走净,去河邊找鬼伏伯。 笑死说搅,一個胖子當(dāng)著我的面吹牛弄唧,可吹牛的內(nèi)容都是我干的侯养。 我是一名探鬼主播逛揩,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼从媚!你這毒婦竟也來了喷众?” 一聲冷哼從身側(cè)響起侮腹,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎加矛,沒想到半個月后斟览,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苛茂,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年躁绸,在試婚紗的時候發(fā)現(xiàn)自己被綠了剥哑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片株婴。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饥努,到底是詐尸還是另有隱情驾诈,我是刑警寧澤乍迄,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布闯两,位于F島的核電站,受9級特大地震影響饥臂,放射性物質(zhì)發(fā)生泄漏稽煤。R本人自食惡果不足惜酵熙,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一绿店、第九天 我趴在偏房一處隱蔽的房頂上張望借嗽。 院中可真熱鬧恶导,春花似錦惨寿、人聲如沸裂垦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闸准。三九已至夷家,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缺谴,已是汗流浹背湿蛔。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留财喳,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓所踊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親误证。 傳聞我的和親對象是個殘疾皇子愈捅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容