SpringBoot?容器刷新前回調(diào)ApplicationContextInitializer
引言
本文將作為Spring系列教程中源碼版塊的第一篇,整個源碼系列將分為兩部分進行介紹;單純的源碼解析,大概率是個吃力沒人看的事情,因此我們將結(jié)合源碼解析,一個是學(xué)習(xí)下別人的優(yōu)秀設(shè)計,一個是站在源碼的角度看一下我們除了日常的CURD之外,還可以干些啥
在Spring的啟動過程中,一系列的操作步驟中,提供了很多的擴展點,供我們來增強;簡單來說就是提供了很多的鉤子,這樣當(dāng)我們在某個節(jié)點執(zhí)行前后,想干點其他的事情時,可以很簡單的支持;
本文介紹的ApplicationContextInitializer
,spring容器在刷新之前會回調(diào)這個接口,從而實現(xiàn)在spring容器未初始化前,干一些用戶希望做的事情
I. 項目準備
本文創(chuàng)建的實例工程采用SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ idea
進行開發(fā)
具體的SpringBoot項目工程創(chuàng)建就不贅述了,核心的pom文件,無需額外的依賴
配置文件 application.yml
, 也沒有什么特殊的配置
源碼工程參考文末的源碼
II. 容器刷新前擴展點實例
1. 自定義ApplicationContextInitializer
當(dāng)我們希望實現(xiàn)一個自定義的上下文初始化時,非常簡單,實現(xiàn)上面這個接口就行了,如
public class ApplicationContextInitializer01 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("ApplicationContextInitializer01"); } }
2. 擴展點注冊
上面自定義一個擴展點,如何使它生效呢?
官方提供了三種方式,如在啟動時,直接進行注冊: springApplication.addInitializers(new ApplicationContextInitializer01());
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); springApplication.addInitializers(new ApplicationContextInitializer01()); try (ConfigurableApplicationContext context = springApplication.run(args)) { } } }
當(dāng)我們的擴展點是放在一個jar包中對外提供時,使用上面的啟動注冊方式顯然是不可行的,此時更推薦的做法就是通過Spring的SPI機制進行注冊
在資源目錄下的META-INF/spring.factories
文件中進行注冊
org.springframework.context.ApplicationContextInitializer=com.git.hui.extention.context.ApplicationContextInitializer02
說明
- 上面SPI的機制非常推薦大家使用,在之前的文章中,
AutoConfiguration
的注冊通常也是使用這種方式
除了上面的兩種注冊方式之外,另外還有一個配置文件的方式,在配置文件application.properties
或 application.yml
中,如下配置
context: initializer: classes: com.git.hui.extention.context.ApplicationContextInitializer03
啟動測試
上面三種注冊方式,我們實現(xiàn)三個自定義的擴展點,然后啟動之后,看一下實際輸出
上面的輸出,可以簡單的得出一個結(jié)論,不同注冊方式的優(yōu)先級(為了更合理的驗證下面的觀點,推薦大家修改下上面三個自定義擴展點名,排除掉是因為擴展名導(dǎo)致的排序問題)
- 配置文件注冊 > SPI注冊 > 啟動時注冊
3. 執(zhí)行順序指定
對于自定義的擴展點實現(xiàn),當(dāng)存在順序關(guān)系時,我們可以通過@Order
注解來實現(xiàn), 如當(dāng)上面的三個擴展點都是通過啟動方式注冊時
@Order(5) public class ApplicationContextInitializer01 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("ApplicationContextInitializer01"); } } @Order(2) public class ApplicationContextInitializer02 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("ApplicationContextInitializer02"); } } @Order(10) public class ApplicationContextInitializer03 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("ApplicationContextInitializer03"); } } @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); springApplication.addInitializers(new ApplicationContextInitializer01(), new ApplicationContextInitializer02(), new ApplicationContextInitializer03()); try (ConfigurableApplicationContext context = springApplication.run(args)) { } } }
輸出實例如下
接著重點來了
- 若上面的三個自定義實現(xiàn),不是相同的注冊方式,如將03采用配置文件方式進行注冊,那么01, 02 依然是啟動注冊
- 則順序是 03 > 02 > 01
- 即
@Order
注解修飾的順序,并不能打破 配置文件 > SPI > 啟動方式注冊的順序
關(guān)于自定義實現(xiàn)類的執(zhí)行順序,規(guī)則如下
- 配置文件 > SPI > 啟動方式
- 相同的注冊方式,可以通過
@Order
注解進行修飾,值越小則優(yōu)先級越高
4. 使用場景示例
最后我們再來看一下,這個擴展點到底有什么用,我們再什么場景下會用到這個呢?
一個經(jīng)??梢钥吹降膽?yīng)用場景如通過它來指定需要激活的配置文件
public class ApplicationContextInitializer03 implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { // 指定激活prod對應(yīng)的配置文件 configurableApplicationContext.getEnvironment().setActiveProfiles("prod"); } }
但是一般也很少見到有人這么干,因為直接使用配置參數(shù)就行了,那么有場景需要這么做么?
答案當(dāng)然是有的,比如現(xiàn)在廣為流行的docker容器部署,當(dāng)我們希望每次都是打同一個鏡像,然后在實際運行的時候,根據(jù)不同的環(huán)境來決定當(dāng)前鏡像到底啟用哪些配置文件,這時就有用了
比如我們通過容器的環(huán)境參數(shù) app.env
來獲取當(dāng)前運行的環(huán)境,如果是prod,則激活application-prod.yml
; 如果是test,則激活application-test.yml
那么此時可以這么干
public class EenvActiveApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { String env = System.getenv("app.env"); if ("prod".equalsIgnoreCase(env)) { configurableApplicationContext.getEnvironment().setActiveProfiles("prod"); } else if ("test".equalsIgnoreCase(env)) { configurableApplicationContext.getEnvironment().setActiveProfiles("test"); } else { throw new RuntimeException("非法的環(huán)境參數(shù):" + env); } } }
5. 小結(jié)
本文作為擴展點的第一篇,通過實現(xiàn)ApplicationContextInitializer
接口,從而達到在spring容器刷新之前做某些事情的目的
通常自定義的ApplicationContextInitializer有三種注冊方式,按照優(yōu)先級如下
- 配置文件 > SPI方式 > 啟動方式注冊
- 相同的注冊方式中,可以使用
@Order
注解來指定優(yōu)先級,值越小優(yōu)先級越高
最后還給出了一個可以應(yīng)用的實例場景,即如何實現(xiàn)一個鏡像在不同的環(huán)境中啟動運行
下一個擴展點我們將介紹如何通過BeanDefinitionRegistryPostProcessor
來實現(xiàn)非Spring生態(tài)的Bean加載使用
III. 不能錯過的源碼和相關(guān)知識點
項目
以上就是SpringBoot 容器刷新前回調(diào)ApplicationContextInitializer的詳細內(nèi)容,更多關(guān)于SpringBoot ApplicationContextInitializer的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA下SpringBoot指定環(huán)境、配置文件啟動操作過程
這篇文章主要介紹了IDEA下SpringBoot指定環(huán)境、配置文件啟動過程,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08JUC中的wait與notify方法實現(xiàn)原理詳解
這篇文章主要介紹了JUC中的wait與notify方法實現(xiàn)原理,在進行wait()之前,就代表著需要爭奪Synchorized,而Synchronized代碼塊通過javap生成的字節(jié)碼中包含monitor?enter和monitor?exit兩個指令2023-03-03MyEclipse如何將項目的開發(fā)環(huán)境與服務(wù)器的JDK 版本保持一致
我們使用MyEclipse開發(fā)Java項目開發(fā)中,偶爾會遇到因項目開發(fā)環(huán)境不協(xié)調(diào),導(dǎo)致這樣那樣的問題,在這里以把所有環(huán)境調(diào)整為JDK1.6 為例,給大家詳細介紹MyEclipse如何將項目的開發(fā)環(huán)境與服務(wù)器的JDK 版本保持一致,需要的朋友參考下吧2024-04-04