SpringBoot成長記6:準備SpringContext容器

SpringBoot成長記6:準備SpringContext容器

上一節的建立了容器物件,核心就是建立了Context和BeanFactory物件,內部初始化了Reader和Scanner,載入了一些內部Bean等。

已經分析的邏輯程式碼如下:

public ConfigurableApplicationContext run(String。。。 args) { //DONE 擴充套件點 SpringApplicationRunListeners listeners。starting(); //DONE 配置檔案的處理和抽象封裝 ConfigurableEnvironment //容器相關處理 //1)核心就是建立了Context和BeanFactory物件,內部初始化了Reader和Scanner,載入了一些內部Bean context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter。class, new Class[] {ConfigurableApplicationContext。class }, context); //2) TODO 容器物件中還需要準備哪些東西? prepareContext(context, environment, listeners, applicationArguments,printedBanner); //3) TODO refreshContext(context); //其他邏輯}

這一節我們來看看,建立容器後,容器物件中還需要準備或者說設定哪些東西,並且還執行了容器哪些擴充套件點呢,一起來看下吧!

prepareContext()的核心脈絡

prepareContext()說白了點其實就是給容器Context和容器DefaultListableBeanFactory設定一些屬性。

你帶著這個思路去理解,就會抓大放小,關注核心即可。大致如下圖:

SpringBoot成長記6:準備SpringContext容器

那麼接下來,就來看下程式碼到底做了些什麼?

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context。setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners。contextPrepared(context); if (this。logStartupInfo) { logStartupInfo(context。getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context。getBeanFactory(); beanFactory。registerSingleton(“springApplicationArguments”, applicationArguments); if (printedBanner != null) { beanFactory。registerSingleton(“springBootBanner”, printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) 。setAllowBeanDefinitionOverriding(this。allowBeanDefinitionOverriding); } if (this。lazyInitialization) { context。addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources Set sources = getAllSources(); Assert。notEmpty(sources, “Sources must not be empty”); load(context, sources。toArray(new Object[0])); listeners。contextLoaded(context); }

先給大家概括一張圖,主要可以劃分為三部分

非常重要的操作、值得一提的操作、不重要的操作

,如下圖:

SpringBoot成長記6:準備SpringContext容器

接下來分別帶大家一起仔細分析下。

觸發的擴充套件點(非常重要的操作)

1)applyInitializers()觸發的擴充套件點操作

這個擴充套件操作,其實就是執行了List的一系列擴充套件操作。你還記得麼?

這個List是在new SpringApplication的時候掃描到的類。還記得下圖麼:

SpringBoot成長記6:準備SpringContext容器

那麼重點是這些擴充套件操作做了什麼呢?

其實概括成一句話就是:

Initializers之後,為context初始化了2個BeanFactoryPostProcessor ,補充了3個ApplicationLisenter

具體每一個ApplicationContextInitializer的執行你可以透過斷點自己仔細看下知道了,其實沒有什麼複雜的邏輯。

整體如下圖所示:

SpringBoot成長記6:準備SpringContext容器

術語普及BeanFactoryPostProcessor是什麼?

BeanFactoryPostProcessor是什麼?之前我們遇見過擴充套件點有SpringApplicationRunListeners和ApplicationContextInitializerSpringApplicationRunListeners透過一個EventPublishingRunListener,對一個List做不同的事件廣播做對流程、容器和配置進行 擴充套件。ApplicationContextInitializer 透過List 其實也是整個流程中的一個擴充套件。而這裡BeanFactoryPostProcessor其實對容器特有的擴充套件,或者說是增強處理。 在容器的使用過程中,執行List 對應的方法,進行擴充套件點的執行。很多第三方框架就是從這地方入手進行擴充套件的,之後會看到的。

2) 觸發listener對容器擴充套件操作。

除了上面ApplicationContextInitializer的擴充套件執行,另一個擴充套件操作的執行就是SpringApplicationRunListeners的擴充套件了。

主要有兩次觸發,listeners#contextPrepared()和listeners。contextLoaded(context);

之前我們分析過,SpringApplicationRunListeners透過一個EventPublishingRunListener,對一個List做不同的事件廣播做對流程、容器和配置進行擴充套件。這裡廣播的是contextPreparedEvent,contextLoadedEvent。

具體細節也不在這裡展開了,簡單的,這兩個事件分別可以概括為如下兩句話:

contextPreparedEvent

的這裡執行了2個ApplicationListener的實現,只不過這兩個listener的onApplicationEvent幾乎是什麼都沒做,

只是註冊兩個日誌物件到容器DefaultListableBeanFactory的singletonObjects屬性。

contextLoadedEvent

執行了,4個ApplicationListener,

其中1個Listener往容器Context中增加了BeafactoryPostProcessor其餘四個Listener基本上什麼都沒幹。

SpringBoot成長記6:準備SpringContext容器

可以看出,這幾個擴充套件點核心其實也沒有做很複雜的事情,就是給容器物件補充設定了一些屬性而已。可以概括如下圖所示:

SpringBoot成長記6:準備SpringContext容器

beanFactory的一些屬性補充(值得一提的操作)

除了上述比較重要的操作外,prepareContext中還有一些比較值得一提的操作。讓我們

1)beanFactory.registerSingleton

註冊兩個物件到容器springApplicationArguments、springBootBanner到beanFactory的 singletonObjects 屬性

2)補充BeanDefinition

BeanDefinitionLoader。load() 補充了 LearnSpringBootApplication 的BeanDefinition到beanFactory中。

這些都比較簡單,整體如下圖:

SpringBoot成長記6:準備SpringContext容器

其實這裡關鍵的是容器內的兩個屬性的設定:

一個是【核心屬性】Map<String,Object> singletonObjects 容器存放Bean物件的集合

一個是【核心屬性】Map<String, BeanDefinition> beanDefinitionMap 容器存放beanDefinition物件的集合

這個是我們這裡想要強調的一點。

設定幾個屬性或者元件(不重要的操作)

1)context#setEnvironment () 設定envrioment 到context中,也就是讓容器持有配置檔案的封裝物件而已。

2)resourceLoader 設定resource類載入器 到context容器 ,預設沒有resourceLoader ,所以這裡什麼沒幹。

3)addConversionService 新增轉換器和格式化器 到context容器,不知道這個元件時做啥的,暫時不是很重要。

4)logStartupInfo() 輸出啟動日誌-PID,啟動檔案目錄

5)logStartupProfileInfo() 輸出啟動日誌-使用的profile

6)設定容器屬性lazyInitialization、allowBeanDefinitionOverriding,預設都是false,不懶載入和不覆蓋BeanDefinition。

SpringBoot成長記6:準備SpringContext容器

小結

說白了,prepareContext()就是給容器Context、BeanFactory設定了一堆屬性和元件,執行了initialize/listener的擴充套件點。

主要給容器如下幾個核心屬性設定值:

singletonObjects 、beanDefinitionMap 、beanFactoryPostProcessors、applicationListeners。

prepareContext()準備完成之後,接下來就是容器關鍵的擴充套件操作執行了,也是很多容器功能和第三方功能的擴充套件之處,我們下一節來一起看下吧!

本文由部落格一文多發平臺 OpenWrite 釋出!