天天看點

《SpringBoot揭秘:快速建構微服務體系》—第3章3.3節SpringApplication:SpringBoot程式啟動的一站式解決方案

本節書摘來自華章出版社《springboot揭秘:快速建構微服務體系》一書中的第3章,第3.3節springapplication:springboot程式啟動的一站式解決方案,作者王福強,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

3.3 springapplication:springboot程式啟動的一站式解決方案

如果非說springboot微架構提供了點兒自己特有的東西,在核心類層面(各種場景下的自動配置一站式插拔子產品,我們下一章再重點介紹),也就是springapplication了。

springapplication将一個典型的spring應用啟動的流程“模闆化”(這裡是動詞),在沒有特殊需求的情況下,預設模闆化後的執行流程就可以滿足需求了;但有特殊需求也沒關系,springapplication在合适的流程結點開放了一系列不同類型的擴充點,我們可以通過這些擴充點對springboot程式的啟動和關閉過程進行擴充。

最“膚淺”的擴充或者配置是springapplication通過一系列設定方法(setters)開放的定制方式,比如,我們之前的啟動類的main方法中隻有一句:

springapplication.run(demoapplication.class, args);

但如果我們想通過springapplication的一系列設定方法來擴充啟動行為,則可以用如下方式進行:

**提示:

設定自定義banner最簡單的方式其實是把ascii art字元畫放到一個資源檔案,然後通過resourcebanner來加載:bootstrap.setbanner(new resourcebanner(new classpathresource("banner.txt")));**

大部分情況下,springapplication已經提供了很好的預設設定,是以,我們不再對這些表層進行探究了,因為對表層之下的東西進行探究才是我們的最終目的。

3.3.1 深入探索springapplication執行流程

springapplication的run方法的實作是我們本次旅程的主要線路, 該方法的主要流程大體可以歸納如下:

1)如果我們使用的是springapplication的靜态run方法,那麼,這個方法裡面首先需要建立一個springapplication對象執行個體,然後調用這個建立好的springapplication的執行個體run方法。在springapplication執行個體初始化的時候,它會提前做幾件事情:

根據classpath裡面是否存在某個特征類(org.springframework.web.context.configurablewebapplicationcontext)來決定是否應該建立一個為web應用使用的applicationcontext類型,還是應該建立一個标準standalone應用使用的applicationcontext類型。

使用springfactoriesloader在應用的classpath中查找并加載所有可用的applicationcontextinitializer。

使用springfactoriesloader在應用的classpath中查找并加載所有可用的applicationlistener。

推斷并設定main方法的定義類。

2)springapplication執行個體初始化完成并且完成設定後,就開始執行run方法的邏輯了,方法執行伊始,首先周遊執行所有通過springfactoriesloader可以查找到并加載的springapplicationrunlistener,調用它們的started()方法,告訴這些springapplicationrunlistener,“嘿,springboot應用要開始執行咯!”。

3)建立并配置目前springboot應用将要使用的environment(包括配置要使用的propertysource以及profile)。

4)周遊調用所有springapplicationrunlistener的environmentprepared()的方法,告訴它們:“目前springboot應用使用的environment準備好咯!”。

5)如果springapplication的showbanner屬性被設定為true,則列印banner(springboot 1.3.x版本,這裡應該是基于banner.mode決定banner的列印行為)。這一步的邏輯其實可以不關心,我認為唯一的用途就是“好玩”(just for fun)。

6)根據使用者是否明确設定了applicationcontextclass類型以及初始化階段的推斷結果,決定該為目前springboot應用建立什麼類型的applicationcontext并建立完成,然後根據條件決定是否添加shutdownhook,決定是否使用自定義的beannamegenerator,決定是否使用自定義的resourceloader,當然,最重要的,将之前準備好的environment設定給建立好的applicationcontext使用。

7)applicationcontext建立好之後,springapplication會再次借助spring-factoriesloader,查找并加載classpath中所有可用的applicationcontext-initializer,然後周遊調用這些applicationcontextinitializer的initialize (applicationcontext)方法來對已經建立好的applicationcontext進行進一步的處理。

8)周遊調用所有springapplicationrunlistener的contextprepared()方法, 通知它們:“springboot應用使用的applicationcontext準備好啦!”

9)最核心的一步,将之前通過@enableautoconfiguration擷取的所有配置以及其他形式的ioc容器配置加載到已經準備完畢的applicationcontext。

10)周遊調用所有springapplicationrunlistener的contextloaded()方法,告知所有springapplicationrunlistener,applicationcontext"裝填完畢"!

11)調用applicationcontext的refresh()方法,完成ioc容器可用的最後一道工序。

12)查找目前applicationcontext中是否注冊有commandlinerunner,如果有,則周遊執行它們。

13)正常情況下,周遊執行springapplicationrunlistener的finished()方法,告知它們:“搞定!”。(如果整個過程出現異常,則依然調用所有springapplicationrunlistener的finished()方法,隻不過這種情況下會将異常資訊一并傳入處理)。

至此,一個完整的springboot應用啟動完畢!

整個過程看起來冗長無比,但其實很多都是一些事件通知的擴充點,如果我們将這些邏輯暫時忽略,那麼,其實整個springboot應用啟動的邏輯就可以壓縮到極其精簡的幾步,如圖3-2所示。

《SpringBoot揭秘:快速建構微服務體系》—第3章3.3節SpringApplication:SpringBoot程式啟動的一站式解決方案

前後對比我們就可以發現,其實springapplication提供的這些各類擴充點近乎“喧賓奪主”,占據了一個spring應用啟動邏輯的大部分“江山”,除了初始化并準備好applicationcontext,剩下的大部分工作都是通過這些擴充點完成的,是以,我們有必要對各類擴充點進行逐一剖析,以便在需要的時候可以信手拈來,為我所用。

3.3.2 springapplicationrunlistener

springapplicationrunlistener是一個隻有springboot應用的main方法執行過程中接收不同執行時點事件通知的監聽者:

對于我們來說,基本沒什麼常見的場景需要自己實作一個spring-applicationrunlistener,即使springboot預設也隻是實作了一個org.spring-framework.boot.context.event.eventpublishingrunlistener,用于在springboot啟動的不同時點釋出不同的應用事件類型(applicationevent),如果有哪些applicationlistener對這些應用事件感興趣,則可以接收并處理。(還記得springapplication執行個體初始化的時候加載了一批applicationlistener,但是在run方法執行流程中卻沒有被使用的絲毫痕迹嗎?eventpublishingrunlistener就是答案!)

假設我們真的有場景需要自定義一個springapplicationrunlistener實作,那麼有一點需要注意,即任何一個springapplicationrunlistener實作類的構造方法(constructor)需要有兩個構造參數,一個構造參數的類型就是我們的org.springframework.boot.springapplication,另外一個就是args參數清單的string[]:

之後,我們可以通過springfactoriesloader立下的規矩,在目前springboot應用的classpath下的meta-inf/spring.factories檔案中進行類似如下的配置:

org.springframework.boot.springapplicationrunlistener=\

com.keevol.springboot.demo.demospringapplicationrunlistener

然後springapplication就會在運作的時候調用它啦!

3.3.3 applicationlistener

applicationlistener其實是老面孔,屬于spring架構對java中實作的監聽者模式的一種架構實作,這裡唯一值得着重強調的是,對于初次接觸springboot,但對spring架構本身又沒有過多接觸的開發者來說,可能會将這個名字與springapplicationrunlistener混淆。

關于applicationlistener我們就不做過多介紹了,如果感興趣,請參考spring架構相關的資料和書籍。

如果我們要為springboot應用添加自定義的applicationlistener,有兩種方式:

1)通過springapplication.addlisteners(..)或者springapplication.setlisteners(..)方法添加一個或者多個自定義的applicationlistener;

2)借助springfactoriesloader機制,在meta-inf/spring.factories檔案中添加配置(以下代碼是為springboot預設注冊的applicationlistener配置):

關于applicationlistener,我們就說這些。

3.3.4 applicationcontextinitializer

applicationcontextinitializer也是spring架構原有的概念,這個類的主要目的就是在configurableapplicationcontext類型(或者子類型)的applicationcontext做refresh之前,允許我們對configurableapplicationcontext的執行個體做進一步的設定或者處理。

實作一個applicationcontextinitializer很簡單,因為它隻有一個方法需要實作:

不過,一般情況下我們基本不會需要自定義一個applicationcontext-initializer,即使springboot架構預設也隻是注冊了三個實作:

如果我們真的需要自定義一個applicationcontextinitializer,那麼隻要像上面這樣,通過springfactoriesloader機制進行配置,或者通過springapplication.addinitializers(..)設定即可。

3.3.5 commandlinerunner

commandlinerunner不是spring架構原有的“寶貝”,它屬于springboot應用特定的回調擴充接口:

commandlinerunner需要大家關注的其實就兩點:

1)所有commandlinerunner的執行時點在springboot應用的application-context完全初始化開始工作之後(可以認為是main方法執行完成之前最後一步)。

2)隻要存在于目前springboot應用的applicationcontext中的任何command-linerunner,都會被加載執行(不管你是手動注冊這個commandlinerunner到ioc容器,還是自動掃描進去的)。

與其他幾個擴充點接口類型相似,建議commandlinerunner的實作類使用@org.springframework.core.annotation.order進行标注或者實作org.springframework.core.ordered接口,便于對它們的執行順序進行調整,這其實十分重要,我們不希望順序不當的commandlinerunner實作類阻塞了後面其他commandlinerunner的執行。

commandlinerunner是很好的擴充接口,大家可以重點關注,我們在後面的擴充和微服務實踐章節會再次遇到它。