初始化國際化組件、事件廣播器革为、注冊事件監(jiān)聽器

本系列大量參考Spring IOC 容器源碼分析哗伯、 【死磕 Spring】—– IOC 總結(jié)程序員囧輝的CSDN

上一篇說到篷角,向beanFactory設(shè)置了各個beanFactory所需的功能焊刹,執(zhí)行BeanFactoryPostProcessors和注冊BeanPostProcessors后。這次簡單提到一些國際化組件恳蹲、事件廣播器和事件監(jiān)聽器(自認(rèn)為這寫的并不好虐块,可以略過)。這篇算是水文

文章內(nèi)容如下:
1.AbstractApplicationContext#refresh()
//初始化國際化組件
2.AbstractApplicationContext#initMessageSource 
//初始化事件廣播器
3.AbstractApplicationContext#initApplicationEventMulticaster 
//在初始化非懶加載的bean之前初始化一下特殊的bean,交由子類實(shí)現(xiàn)
4. AbstractApplicationContext#onRefresh() 
//注冊事件監(jiān)聽器
5. AbstractApplicationContext#registerListeners() 
//實(shí)例化所有剩余的非懶加載單例 bean(簡單介紹)
6. AbstractApplicationContext# finishBeanFactoryInitialization(beanFactory) 

1.refresh

public void refresh() throws BeansException, IllegalStateException {
   // 來個鎖嘉蕾,不然 refresh() 還沒結(jié)束贺奠,你又來個啟動或銷毀容器的操作,那不就亂套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 準(zhǔn)備工作错忱,記錄下容器的啟動時(shí)間儡率、標(biāo)記“已啟動”狀態(tài)、處理配置文件中的占位符
      prepareRefresh();

      // 這步比較關(guān)鍵以清,這步完成后儿普,配置文件就會解析成一個個 Bean 定義,注冊到 BeanFactory 中掷倔,
      // 當(dāng)然眉孩,這里說的 Bean 還沒有初始化,只是配置信息都提取出來了勒葱,
      // 注冊也只是將這些信息都保存到了注冊中心(說到底核心是一個 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 設(shè)置 BeanFactory 的類加載器浪汪,添加幾個 BeanPostProcessor,手動注冊幾個特殊的 bean
      // 這塊待會會展開說
      prepareBeanFactory(beanFactory);

      try {
         // 【這里需要知道 BeanFactoryPostProcessor 這個知識點(diǎn)凛虽,Bean 如果實(shí)現(xiàn)了此接口死遭,
         // 那么在容器初始化以后,Spring 會負(fù)責(zé)調(diào)用里面的 postProcessBeanFactory 方法凯旋⊙教叮】

         // 這里是提供給子類的擴(kuò)展點(diǎn),到這里的時(shí)候瓦阐,所有的 Bean 都加載蜗侈、注冊完成了篷牌,但是都還沒有初始化
         // 具體的子類可以在這步的時(shí)候添加一些特殊的 BeanFactoryPostProcessor 的實(shí)現(xiàn)類或做點(diǎn)什么事
         postProcessBeanFactory(beanFactory);
         // 調(diào)用 BeanFactoryPostProcessor 各個實(shí)現(xiàn)類的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注冊 BeanPostProcessor 的實(shí)現(xiàn)類睡蟋,注意看和 BeanFactoryPostProcessor 的區(qū)別
         // 此接口兩個方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 兩個方法分別在 Bean 初始化之前和初始化之后得到執(zhí)行。注意枷颊,到這里 Bean 還沒初始化
         registerBeanPostProcessors(beanFactory);

         // 初始化當(dāng)前 ApplicationContext 的 MessageSource戳杀,國際化這里就不展開說了该面,不然沒完沒了了
         initMessageSource();

         // 初始化當(dāng)前 ApplicationContext 的事件廣播器,這里也不展開了
         initApplicationEventMulticaster();

         // 從方法名就可以知道信卡,典型的模板方法(鉤子方法)隔缀,
         // 具體的子類可以在這里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注冊事件監(jiān)聽器,監(jiān)聽器需要實(shí)現(xiàn) ApplicationListener 接口傍菇。這也不是我們的重點(diǎn)猾瘸,過
         registerListeners();

         // 重點(diǎn),重點(diǎn)丢习,重點(diǎn)
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后牵触,廣播事件,ApplicationContext 初始化完成
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 銷毀已經(jīng)初始化的 singleton 的 Beans咐低,以免有些 bean 會一直占用資源
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // 把異常往外拋
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

2.AbstractApplicationContext#initMessageSource 初始化國際化組件

先來看看initMessageSource方法

    protected void initMessageSource() {
        //獲取Bean工廠揽思,一般是DefaultListBeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //首先判斷beanFactory的BeanDifinition緩存里有沒有id為messageSource的BeanDifinition
        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
            //如果有,則從Bean工廠得到這個bean對象
            this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            //當(dāng)父類Bean工廠不為空见擦,并且這個bean對象是HierarchicalMessageSource類型
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                //強(qiáng)轉(zhuǎn)為HierarchicalMessageSource
                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;

                if (hms.getParentMessageSource() == null) {
                    //如果父消息源為空钉汗,那么設(shè)置一個父消息源
                    // (如果父類是AbstractApplicationContext,則返回父類的messageSource鲤屡,否則直接返回一個父類)
                    hms.setParentMessageSource(getInternalParentMessageSource());
                }
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Using MessageSource [" + this.messageSource + "]");
            }
        }
        else {
            //如果沒有這個id為messageSource的BeanDifinition损痰,新建DelegatingMessageSource類作為messageSource的Bean
            //因?yàn)镈elegatingMessageSource類實(shí)現(xiàn)了HierarchicalMessageSource接口,而這個接口繼承了MessageSource這個類
            //因此實(shí)現(xiàn)了這個接口的類酒来,都是MessageSource的子類徐钠,因此DelegatingMessageSource也是一個MessageSource
            DelegatingMessageSource dms = new DelegatingMessageSource();
            //給這個DelegatingMessageSource添加父類消息源
            dms.setParentMessageSource(getInternalParentMessageSource());
            this.messageSource = dms;
            //將這個messageSource實(shí)例注冊到Bean工廠中
            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
            }
        }
    }

簡單解釋下國際化的意義;
假設(shè)我們正在開發(fā)一個支持多國語言的Web應(yīng)用程序,要求系統(tǒng)能夠根據(jù)客戶端的系統(tǒng)的語言類型返回對應(yīng)的界面:英文的操作系統(tǒng)返回英文界面役首,而中文的操作系統(tǒng)則返回中文界面——這便是典型的i18n國際化問題尝丐。
對于有國際化要求的應(yīng)用系統(tǒng),我們不能簡單地采用硬編碼的方式編寫用戶界面信息衡奥、報(bào)錯信息等內(nèi)容爹袁,而必須為這些需要國際化的信息進(jìn)行特殊處理。簡單來說矮固,就是為每種語言提供一套相應(yīng)的資源文件失息,并以規(guī)范化命名的方式保存在特定的目錄中,由系統(tǒng)自動根據(jù)客戶端語言選擇適合的資源文件档址。

3.AbstractApplicationContext#initApplicationEventMulticaster 初始化事件廣播器

protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //首先判斷beanFactory的BeanDifinition緩存里有沒有id為applicationEventMulticaster的BeanDifinition
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            //如果有盹兢,就實(shí)例化這個BeanDifinition并賦值給本地的變量applicationEventMulticaster
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        } else {
            //如果沒有,那么就直接創(chuàng)建一個SimpleApplicationEventMulticaster賦值給本地變量
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            // 并且把這個SimpleApplicationEventMulticaster對象注冊到beanFactory的緩存中守伸,id為applicationEventMulticaster
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }

我們就可以在其他組件要派發(fā)事件绎秒,自動注入這個applicationEventMulticaster,他可以管理很多個ApplicationListener對象尼摹。并將事件發(fā)布給這些監(jiān)聽器见芹。

4. AbstractApplicationContext#onRefresh() 在初始化非懶加載的bean之前初始化一下特殊的bean

onRefresh()則是一個空方法剂娄,交由子類自己實(shí)現(xiàn),在初始化非懶加載的bean之前初始化一下特殊的bean玄呛。(看自己的業(yè)務(wù)要怎么做啦)

5. AbstractApplicationContext#registerListeners() 注冊事件監(jiān)聽器

    protected void registerListeners() {
        // 獲取硬編碼進(jìn)來的ApplicationListeners(指非配置的阅懦,通過代碼直接添加進(jìn)來的)
        //注冊 到我們上面剛初始化的ApplicationEventMulticaster 中。
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        //從容器中獲取所有實(shí)現(xiàn)了ApplicationListener接口的bd的bdName,注冊到ApplicationEventMulticaster 中
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        // 發(fā)布早期 earlyApplicationEvents 事件徘铝,到 ApplicationListener 們,默認(rèn)為空
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }

有關(guān)Spring的事件機(jī)制簡單了解可以參考下面這兩個鏈接耳胎,有機(jī)會再細(xì)說。

spring事件機(jī)制
Spring事件驅(qū)動過程分析

6. AbstractApplicationContext# finishBeanFactoryInitialization(beanFactory) 實(shí)例化所有剩余的非懶加載單例 bean(簡單介紹)

上面都是簡單介紹惕它,接下來就是主菜了

該方法會實(shí)例化所有剩余的非懶加載單例 bean场晶。除了一些內(nèi)部的 bean、實(shí)現(xiàn)了 BeanFactoryPostProcessor 接口的 bean怠缸、實(shí)現(xiàn)了BeanPostProcessor 接口的 bean(上述的這三類在前面方法里已經(jīng)被實(shí)現(xiàn)了)诗轻,其他的非懶加載單例bean 都會在這個方法中被實(shí)例化,并且 BeanPostProcessor 的觸發(fā)也是在這個方法中揭北。
這邊我們簡單介紹下扳炬,這篇就簡單水一下吧,因?yàn)檫@個方法比較重要搔体,調(diào)用棧又特別深恨樟,需要開好幾個章節(jié)來講。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疚俱,一起剝皮案震驚了整個濱河市劝术,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呆奕,老刑警劉巖养晋,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梁钾,居然都是意外死亡绳泉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門姆泻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來零酪,“玉大人,你說我怎么就攤上這事拇勃∷奈” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵方咆,是天一觀的道長月腋。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么罗售? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任辜窑,我火速辦了婚禮钩述,結(jié)果婚禮上寨躁,老公的妹妹穿的比我還像新娘。我一直安慰自己牙勘,他們只是感情好职恳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著方面,像睡著了一般放钦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恭金,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天操禀,我揣著相機(jī)與錄音,去河邊找鬼横腿。 笑死颓屑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耿焊。 我是一名探鬼主播揪惦,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼罗侯!你這毒婦竟也來了器腋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钩杰,失蹤者是張志新(化名)和其女友劉穎纫塌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讲弄,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡护戳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垂睬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媳荒。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖驹饺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赏壹,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布昔瞧,位于F島的核電站指蚁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏自晰。R本人自食惡果不足惜凝化,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一酬荞、第九天 我趴在偏房一處隱蔽的房頂上張望搓劫。 院中可真熱鬧混巧,春花似錦枪向、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至傍衡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聪舒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工箱残, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人被辑。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像谈山,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宏怔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355