我們說SpringBoot是Spring框架對“約定優先于配置(Convention Over Configuration)"理念的最佳實踐的產物,一個典型的SpringBoot應用本質上其 實就是一個基于Spring框架的應用
如果非說SpringBoot微框架提供了點兒自己特有的東西,在核心類層面(除了各種場景下的自動配置一站式插拔模塊),也就是SpringApplication了。
SpringApplication將一個典型的Spring應用啟動的流程“模板化”(這里是動詞),在沒有特殊需求的情況下,默認模板化后的執行流程就可以滿足需求了;但有特殊需求也沒有關系,SpringApplication在合適的流程結點開放了一系列不同類型的擴展點,我們可以通過這些擴展點對SpringBoot程序的啟動和關閉過程進行擴展。
最簡單的擴展或配置是SpringApplication通過一系列設置方法(setters)開發的定制方式 . . .
大部分情況下,SpringApplication已經提供了很好的默認設置,所以,我們不再對這些表層進行探究了,因為對表層之下的東西進行探究才是我們的最終目的。
一、SpringApplication執行流程
SpringApplication的(靜態)run方法的實現的主要流程大體可以歸納如下:
-
SpringApplication實例初始化
SpringApplication實例初始化時,它會提前做幾件事情:
- 根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型,還是應該創建一個標準Standalone應用使用的ApplicationContext類型。
- 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationListener。
- 推斷并設置main方法的定義類。
-
SpringApplication實例初始化完成并且完成設置后,就開始執行run方法的邏輯了
方法執行伊始,首先遍歷執行所有通過
SpringFactoriesLoader
可以查找到并加載的SpringApplicationRunListener
,調用它們的started()方法,告訴這些SpringApplicationRunListener,SpringBoot應用要開始執行了 -
創建并配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
-
遍歷調用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴它們:當前SpringBoot應用使用的Environment準備了
-
如果SpringApplication的showBanner屬性被設置為true,則打印Banner.Mode
-
根據用戶是否明確設置了applicationContextClass類型以及初始化階段的推斷結果,決定該為當前SpringBoot應用創建什么類型的ApplicationContext并創建完成,然后根據條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設置給創建好的ApplicationContext使用。
-
ApplicationContext創建好之后,SpringApplication會再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調用這些ApplicationContextInitializer的initialize (applicationContext)方法來對已經創建好的ApplicationContext進行進一步的處理。
-
遍歷調用所有SpringApplicationRunListener的contextPrepared()方法, 通知它們:“SpringBoot應用使用的ApplicationContext準備好啦!”
-
最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經準備完畢的ApplicationContext。
-
遍歷調用所有SpringApplicationRunListener的contextLoaded()方法,告知所有SpringApplicationRunListener,ApplicationContext”裝填完畢”!
-
調用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
-
查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執行它們。
-
正常情況下,遍歷執行SpringApplicationRunListener的finished()方法,告知它們:“搞定!”。
(如果整個過程出現異常,則依然調用所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常信息一并傳入處理)。
整個過程看起來冗長無比,但其實很多都是一些事件通知的擴展點,如果我們將這些邏輯暫時忽略,那么,其實整個SpringBoot應用啟動的邏輯就可以壓縮到極其精簡的幾步。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UstTIBSw-1614751600844)(https://i.loli.net/2018/01/21/5a646ee77ff52.jpg)]
前后對比我們就可以發現,其實SpringApplication提供的這些各類擴展點 近乎“喧賓奪主”,占據了一個Spring應用啟動邏輯的大部分“江山”,除了初 始化并準備好ApplicationContext,剩下的大部分工作都是通過這些擴展點完成 的,所以,我們有必要對各類擴展點進行逐一剖析,以便在需要的時候可以信 手拈來,為我所用。
二、SpringApplicationRunListener
SpringApplicationRunListener是一個只有SpringBoot應用的main方法執行過程中接收不同執行時點事件通知的監聽者:
public interface SpringApplicationRunListener { void started(); void environmentPrepared( ConfigurableEnvironment environment); void contextPrepared( ConfigurableApplicationContext context); void contextLoaded( ConfigurableApplicationContext context); void finished( ConfigurableApplicationContext context, Throwable exception);
}
對于我們來說,基本沒什么常見的場景需要自己實現一個SpringApplicationRunListener,即使SpringBoot默認也只實現了一個org.springframework.boot.context.event.EventPublishingRunListener,用于在SpringBoot啟動的不同時點發布不同的應用事件類型(ApplicationEvent),如果有哪些ApplicationListener對這些應用事件感興趣,則可以接收并處理。
假設我們真的有場景需要自定義一個SpringApplicationRunListener實現,那么有一點需要注意,即任何一個SpringApplicationRunListener實現類的構造方法(Constructor)需要有兩個構造參數,一個構造參數的類型就是我們的org.springframework.boot.SpringApplication,另外一個就是args參數列表的String[]:
public class DemoSpringApplicationRunListener implements SpringApplicationRunListener { @Override public void started() {// do whatever you want to do } @Override public void environmentPrepared( ConfigurableEnvironment environment) { // do whatever you want to do } @Override public void contextPrepared( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void contextLoaded( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void finished( ConfigurableApplicationContext context, Throwable exception) { // do whatever you want to do }
之后,我們可以通過SpringFactoriesLoader立下的規矩,在當前SpringBoot應用的classpath下的META-INF/spring.factories文件中進行類似如下的配置:
org.springframework.boot.SpringApplicationRunListener=\
com.self.springboot.demo.DemoSpringApplicationRunListener
然后SpringApplication就會在運行的時候調用它啦!
三、ApplicationListener
ApplicationListener其實是老面孔,屬于Spring框架對Java中實現的監聽者模式的一種框架實現,這里唯一值得著重強調的是,對于初次接觸SpringBoot,但對Spring框架本身又沒有過多接觸的開發者來說,可能會將這個名字與SpringApplicationRunListener混淆。
如果我們要為SpringBoot應用添加自定義的ApplicationListener,有兩種方式:
通過SpringApplication.addListeners(… )或者SpringApplication.setListeners(… )方法添加一個或者多個自定義的ApplicationListener;
借助SpringFactoriesLoader機制,在META-INF/spring.factories文件中添加配置(以下代碼是為SpringBoot默認注冊的ApplicationListener配置)
org.springframework.context.ApplicationListener=
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.cloudfoundry.VcapApplicationListener,
org.springframework.boot.context.FileEncodingApplicationListener,
org.springframework.boot.context.config.AnsiOutputApplicationListener,
org.springframework.boot.context.config.ConfigFileApplicationListener,
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicat- ionListener,
org.springframework.boot.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.logging.LoggingApplicationListener
關于ApplicationListener,我們就說這些。
四、ApplicationContextInitializer
ApplicationContextInitializer也是Spring框架原有的概念,這個類的主要目的是在ConfigurableApplictaionContext類型(或者子類型)的ApplicationContext的refresh之前,允許我們對ConfigurableApplicationContext的實例做進一步的設置和處理。
實現一個ApplicationContextInitializer很簡單,因為它只有一個方法需要實現:
public class DemoApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize( ConfigurableApplicationContext applicationContext) { // do whatever you want with applicationContext, // e. g. applicationContext. registerShutdownHook();}
}
不過,一般情況下我們基本不會需要自定義一個ApplicationContextInitializer,即使SpringBoot框架默認也只是注冊了三個實現:
不過,一般情況下我們基本不會需要自定義一個ApplicationContextInitializer,即使SpringBoot框架默認也只是注冊了三個實現:
org. springframework. context. ApplicationContextInitializer=\
org. springframework. boot. context. ConfigurationWarningsApplication- ContextInitializer,\
org. springframework. boot. context. ContextIdApplicationContextInitia- lizer,\
org. springframework. boot. context. config. DelegatingApplicationContex- tInitializer如果我們真的需要自定義一個ApplicationContextInitializer,那么只要像上面這樣,通過SpringFactoriesLoader機制進行配置,或者通過SpringApplication.addInitializers(…) 設置即可。
五、CommandLineRunner
CommandLineRunner屬于SpringBoot應用特定的回調擴展接口:
public interface CommandLineRunner { void run( String... args) throws Exception;
}
CommandLineRunner需要關注的兩點:
-
所有CommandLineRunner的執行時點在SpringBoot應用的ApplicationContext完全初始化開始工作之后(可以認為是main方法執行完成之前最后一步)
-
只要存在于當前SpringBoot應用的ApplicationContext中的任何CommandLineRunner,都會被加載執行(不管你是手動注冊這個CommandLineRunner到IoC容器,還是自動掃描進去的)
與其他幾個擴展點接口類型相似,建議CommandLineRunner的實現類使用@org.springframework.core.annotation.Order進行標注或者實現org.springframework.core.Ordered接口,便于對它們的執行順序進行調整,這其實十分重要,我們不希望順序不當的CommandLineRunner實現類阻塞了后面其他CommandLineRunner的執行。
參考:
- 《SpringBoot揭秘+快速構建微服務體系》 第三章