SpringBoot成長記10:Bean實例化的流程和設(shè)計

file

之前我們已經(jīng)分析SpringBoot在run方法時控轿,它會執(zhí)行的refresh()容器的操作挽荠。

file

在SpringBoot中啦扬,refresh()有十幾個方法镀迂,每個方法的大重要程度是不一樣的内狗,我們通過抓大放小的方式怪嫌,分析處理上圖3個核心邏輯。

并且已經(jīng)研究完了invokeBeanFactoryPostProcessors和onRefresh的邏輯柳沙,分析它們的原理和設(shè)計思想岩灭。

之前主要分析的:

原理有對SpringBoot的自動裝配配置如何做到的、第三方技術(shù)如何進(jìn)行擴(kuò)展的赂鲤、tomcat如何啟動的

設(shè)計思想有SpringBoot擴(kuò)展接口設(shè)計噪径、有對Tomcat組件的擴(kuò)展設(shè)計、Spring容器抽象思想的設(shè)計数初、SpringBoot和第三方技術(shù)整合的擴(kuò)展設(shè)計等等找爱。

refresh()還有一個非常關(guān)鍵的操作,就是bean的實例化,今天我們就來看下refresh最后一個方法—finishBeanFactoryInitialization泡孩。

看看它如何執(zhí)行Bean實例化的流程和設(shè)計的车摄。

finishBeanFactoryInitialization之前和之后的操作概況

file

可以看到,bean的實例化前后,還是做了一些事情的,主要執(zhí)行的是一些擴(kuò)展點吮播,比如listener的擴(kuò)展點執(zhí)行变屁、LifycycleProcessor的執(zhí)行。

這一節(jié)我們核心關(guān)系的是bean創(chuàng)建流程和設(shè)計意狠,所以我們抓大放小粟关,過就可以。直接來看下面創(chuàng)建bean吧环戈。

preInstantiateSingletons方法的核心脈絡(luò)

其實bean的實例化大家或多或少都知道一些誊役。所以我不會特別詳細(xì)的每一個方法都帶大家看。

我們還是本著先脈絡(luò)后細(xì)節(jié)谷市,最后思考的思想來分析Bean的實例化,當(dāng)你用這種方法分析玩后击孩,和你之前分析對吧下有什么區(qū)別迫悠,可以感受下。

如果之后大家有訴求需要精讀Bean實例化的邏輯巩梢,我之后可以考慮放在Spring成長記中為大家仔細(xì)帶來Bean實例化的分析创泄。

這里我們主要過下它的核心源碼就可以了。

由于SpringBoot是為了更好的使用Spring括蝠,它是基于Spring的鞠抑。如果你懂Spring實例化,這塊其實非常好理解的忌警。

讓我們來看下吧!

finishBeanFactoryInitialization主要的代碼如下所示:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // Initialize conversion service for this context.
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

        // Register a default embedded value resolver if no bean post-processor
        // (such as a PropertyPlaceholderConfigurer bean) registered any before:
        // at this point, primarily for resolution in annotation attribute values.
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
        }

        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();

        // Instantiate all remaining (non-lazy-init) singletons.
        beanFactory.preInstantiateSingletons();
    }

這個方法的脈絡(luò)其實比較清楚搁拙,其實最關(guān)鍵的只有一句話:

// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

其余的都是給beanFactory補(bǔ)充點東西而已,不是很關(guān)鍵法绵。

這句話從注釋很清楚的說了箕速,是根據(jù)BeanDefinition實例化所有剩余的單例非延遲初始化的bean。

整個方法我通過先脈絡(luò)的思想朋譬,給大家概括了下:

    public void preInstantiateSingletons() throws BeansException {
        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
        //遍歷所有beanDefinition盐茎,基于beanDefinition創(chuàng)建bean
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (isFactoryBean(beanName)) {
              //做一些處理FactoryBean
              //再從容器中獲取bean,如果不存在就創(chuàng)建
              getBean(beanName);
            }else{
              //從容器中獲取bean徙赢,如果不存在就創(chuàng)建
              getBean(beanName);
            }
      
        }

        // 執(zhí)行Bean的擴(kuò)展操作
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                    smartSingleton.afterSingletonsInstantiated();
        
            }
        }
    }

上面的代碼是我對源碼精簡后的邏輯字柠,它的脈絡(luò)非常清晰了整體如下圖所示:

file

創(chuàng)建bean的核心流程

當(dāng)你知道了preInstantiateSingletons方法的核心脈絡(luò)后,它主要觸發(fā)的是getBean方法狡赐,之后觸發(fā)了doGetBean窑业。

doGetBean整個方法還是比較復(fù)雜的,我還是通過先脈絡(luò)的思想枕屉,抓大放小后数冬,給大家精簡了下源碼,精簡后如下:

    @Overrid
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null, null, false);
    }  
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;
    
        //嘗試獲取bean,如果容器中有了拐纱,就不需要創(chuàng)建了
        Object bean = getSingleton(beanName);
     
    
    if(bean == nul){
        //getFromParentBeanfacotry 當(dāng)前容器沒有bean對應(yīng)的單例對象铜异,嘗試從父容器獲取,如果父容器為空秸架,則不做處理
        //默認(rèn)父容器空揍庄,這里略過
       
        //當(dāng)前bean的依賴dependsOn處理,遞歸調(diào)用getBean
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
          getBean(dep);         
        }

        // 單例還是多例的方式創(chuàng)建bean
        if (mbd.isSingleton()) {
             bean = createBean(beanName, mbd, args);
        }else if (mbd.isPrototype()) {
            bean = createBean(beanName, mbd, args);
        }else {
            String scopeName = mbd.getScope();
            final Scope scope = this.scopes.get(scopeName);
            bean = createBean(beanName, mbd, args);
        }

    }
    
       //bean的擴(kuò)展东抹,使用轉(zhuǎn)換器處理bean
       T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);     
       
        return (T) bean;
}

doGetBean整體代碼方法通過蚂子,抓大放小,分析脈絡(luò)后缭黔,其實已經(jīng)很清晰了食茎。主要做了這么幾件事:

1)getSingleton從容器Beanfactory中的map屬性,獲取bean馏谨,如果非空别渔,直接就可以使用

2)如果容器中沒有這個bean,通過判斷是否單例惧互,來執(zhí)行對應(yīng)的創(chuàng)建bean方法createBean

3)如果當(dāng)前bean的依賴dependsOn處理哎媚,遞歸調(diào)用getBean

4)最后執(zhí)行了bean的擴(kuò)展,使用轉(zhuǎn)換器處理bean

doGetBean方法的大體脈絡(luò)喊儡,基本上就是這四步拨与,如下圖所示:

file

真正創(chuàng)建bean的邏輯,到現(xiàn)在我們還是沒有看到艾猜,需要繼續(xù)向下找买喧。doGetBean之后下面就會執(zhí)行createBean

同理我們使用之前的方法繼續(xù)梳理脈絡(luò)、抓大放小得到如下代碼:

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {匆赃、
    //Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
    Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
    if (bean != null) {
       return bean;
    }
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    return beanInstance;
}

這里createBean其實主要執(zhí)行了resolveBeforeInstantiation和doCreateBean方法岗喉。

1)resolveBeforeInstantiation方法從注解上看,是給動態(tài)代理創(chuàng)建一個對象的機(jī)會炸庞,也就說钱床,可以通過BeanPostProcessor使用動態(tài)代理對某些bean直接進(jìn)行創(chuàng)建。這個非常有意思埠居,也很關(guān)鍵查牌,你想想是不是有的技術(shù)就是利用這里進(jìn)行創(chuàng)建的呢?

2)如果不滿足第一個條件滥壕,就會使用doCreateBean來創(chuàng)建Bean

整個邏輯如下圖所示:

file

這里終于找到了一種bean創(chuàng)建的方式了纸颜,之后應(yīng)該還有其他方式,比如反射绎橘。我們繼續(xù)來看doCreateBean胁孙。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    //創(chuàng)建bean唠倦,bean的實例化
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    //bean屬性的填充
    populateBean(beanName, mbd, instanceWrapper);
    //bean 擴(kuò)展點的觸發(fā)
    initializeBean(beanName, exposedObject, mbd);
    //bean 擴(kuò)展點的添加
    registerDisposableBeanIfNecessary(beanName, bean, mbd);
    return exposedObject;
}

整個doCreateBean方法,通過我們之前的思路涮较,一樣精簡完后稠鼻,脈絡(luò)也很清楚。主要有:

1)創(chuàng)建bean狂票,bean的實例化

2)bean屬性的處理

3)bean 擴(kuò)展點的觸發(fā)

4)bean 擴(kuò)展點的添加

doCreateBean的脈絡(luò)如下圖所示:

file

這里可以看到候齿,除了之前動態(tài)代理的截胡,終于找到了bean實例化闺属,創(chuàng)建bean的地方了慌盯。

其余對bean屬性處理和擴(kuò)展點,我們先不看掂器。重點研究清楚bean的創(chuàng)建再說亚皂。

createBeanInstance同樣被我們抓大放小后的代碼如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
   // Make sure bean class is actually resolved at this point.
   Class<?> beanClass = resolveBeanClass(mbd, beanName);

   //instanceSupplier方式創(chuàng)建bean
   Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
   if (instanceSupplier != null) {
      return obtainFromSupplier(instanceSupplier, beanName);
   }
   //FactoryMethod方式創(chuàng)建bean
   if (mbd.getFactoryMethodName() != null) {
      return instantiateUsingFactoryMethod(beanName, mbd, args);
   }

   //反射的方式創(chuàng)建bean
   if (resolved) {
      if (autowireNecessary) {
         return autowireConstructor(beanName, mbd, null, null);
      }
      else {
         return instantiateBean(beanName, mbd);
      }
   }

   return instantiateBean(beanName, mbd);
}

你會發(fā)現(xiàn)有多種創(chuàng)建的bean的方式,并不是只有反射国瓮,方式主要有:

1)instanceSupplier方式創(chuàng)建bean

2)FactoryMethod方式創(chuàng)建bean

3)反射的方式創(chuàng)建bean

那么再算上之前動態(tài)代理創(chuàng)建bean灭必、Factorybean創(chuàng)建的bean。一共已經(jīng)有5種可以創(chuàng)建bean的方式巍膘, 但是一般我們還都是通過反射創(chuàng)建的居多。

你可能沒有見過其他的方式創(chuàng)建的bean芋簿,但是我在一些技術(shù)中也見過一些峡懈,給大家分享下:

比如

Shiro框架使用Factorybean創(chuàng)建的就比較多

動態(tài)代理創(chuàng)建bean、dubbo就有很多這么創(chuàng)建的

FactoryMethod創(chuàng)建的bean与斤,SpringBoot自動裝配的時候有時候會用到肪康,之前tomcat啟動的時候,TomcatServletWebServerFactory是不是就用到了撩穿。

instanceSupplier是Spring5之后才有的磷支,目前我還沒有見到啥框架用到過....

好了不管如何,最終你獲得了如下的一張圖食寡,總結(jié)bean的創(chuàng)建方式:

file

到這里bean 的創(chuàng)建的流程雾狈,我們就大體分析完了,由于我們不是分析Spring抵皱,所以就不會再對這里面的每個細(xì)節(jié)進(jìn)行分析了善榛。

之后有機(jī)會出Spring成長記的時候,我可以在帶大家詳細(xì)分析吧呻畸。

只是熟悉SpringBoot中移盆,Spring實例化bean的流程了解到這里基本就可以了。

創(chuàng)建的bean整個流程可以總結(jié)下圖的幾步:

file

Bean實例化的擴(kuò)展點設(shè)計

最后我們來看下Bean擴(kuò)展設(shè)計吧伤为,這個其實網(wǎng)上都有一大堆了咒循,但是你一定要注意,你得會區(qū)分優(yōu)劣的文章、提煉關(guān)鍵點叙甸,要對這些有自己的思考才行颖医。

就像我現(xiàn)在給大家分享的,就是我對擴(kuò)展點的思考蚁署。這個是我一直給大家強(qiáng)調(diào)的便脊。

好了,我來簡單說下光戈,我對Bean的擴(kuò)展點設(shè)計的思考和理解吧哪痰。

在Spring中,Bean實例化的時候久妆,有很多擴(kuò)展點晌杰,這些擴(kuò)展點其實還是很關(guān)鍵的。

比如:在Spring的生態(tài)系統(tǒng)中筷弦,很多技術(shù)都是通過Bean的擴(kuò)展點來實現(xiàn)的肋演。而且包括第三方的技術(shù),比如bytetcc分布式事物框架的實現(xiàn)原理和Bean擴(kuò)展點BeanPostProcessor就有很大的關(guān)系烂琴、大企業(yè)自研框架爹殊,可以實現(xiàn)自定義注解的處理、自定義配置文件的處理奸绷、給自己開發(fā)的bean設(shè)置屬性等等梗夸。

那Bean的擴(kuò)展點設(shè)計了哪些呢?我給大家花了一個圖号醉,基本就能概況常見的擴(kuò)展點了反症,當(dāng)然可能還有一些其他的擴(kuò)展點,不管有多少個畔派,它們都是擴(kuò)展點铅碍,合理利用就好了 ,這個是關(guān)鍵线椰。

Bean實例化時胞谈,常見的擴(kuò)展點的設(shè)計如圖所示:

file

小結(jié)

如果你去看Bean的實例化的整個流程,其實其中的細(xì)節(jié)很復(fù)雜的憨愉,如果在復(fù)雜中找到關(guān)鍵點呜魄,是SpringBoot成長記以來,一直想要教給大家的莱衩。

最后通過對Bean實例化的分析爵嗅,讓大家熟練的應(yīng)用了之前的學(xué)到的先脈絡(luò)后細(xì)節(jié)、抓大放小笨蚁、連蒙帶猜睹晒、畫核心組件圖趟庄、流程圖、看注釋等思想和方法伪很。

而且每看一陣子邏輯戚啥,要對它做出思考,思考它的設(shè)計锉试,它的擴(kuò)展猫十、它的思想理念等等。

這個是常用的一套方法論呆盖,可能不適合所有場景拖云,但是大多情況可以讓你閱讀源碼或者研究技術(shù)原理的時候,不那么不安应又,不會覺得它們太難宙项,可以讓你有方向、有方法株扛。

今天的內(nèi)容尤筐,其實沒有什么要總結(jié)的,比較重要的就是最后bean 的擴(kuò)展設(shè)計洞就,我就不在重復(fù)了盆繁。

好了,我們下一節(jié)再見旬蟋!

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布油昂!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咖为,隨后出現(xiàn)的幾起案子秕狰,更是在濱河造成了極大的恐慌稠腊,老刑警劉巖躁染,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異架忌,居然都是意外死亡吞彤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門叹放,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饰恕,“玉大人,你說我怎么就攤上這事井仰÷袂叮” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵俱恶,是天一觀的道長雹嗦。 經(jīng)常有香客問我范舀,道長,這世上最難降的妖魔是什么了罪? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任锭环,我火速辦了婚禮,結(jié)果婚禮上泊藕,老公的妹妹穿的比我還像新娘辅辩。我一直安慰自己,他們只是感情好娃圆,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布玫锋。 她就那樣靜靜地躺著,像睡著了一般踊餐。 火紅的嫁衣襯著肌膚如雪景醇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天吝岭,我揣著相機(jī)與錄音三痰,去河邊找鬼。 笑死窜管,一個胖子當(dāng)著我的面吹牛散劫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幕帆,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼获搏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了失乾?” 一聲冷哼從身側(cè)響起常熙,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碱茁,沒想到半個月后裸卫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡纽竣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年墓贿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜓氨。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡聋袋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出穴吹,到底是詐尸還是另有隱情幽勒,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布港令,位于F島的核電站啥容,受9級特大地震影響棘钞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜干毅,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一宜猜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硝逢,春花似錦姨拥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徽缚,卻和暖如春憨奸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凿试。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工排宰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人那婉。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓板甘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親详炬。 傳聞我的和親對象是個殘疾皇子盐类,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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