spring-boot自定義容器初始化組件

前言
前段時間我們在分享spring boot啟動過程的時候,提到有幾個dome要分享下,至于分享這些dome的原因主要有以下幾點(diǎn),第一點(diǎn)是結(jié)合spring boot啟動過程從實(shí)踐的角度看這些組件的用法和作用;第二點(diǎn)是,從源碼的角度發(fā)掘spring boot潛在的高級知識點(diǎn),學(xué)習(xí)和消化這些知識點(diǎn),實(shí)現(xiàn)自我提升,所以從今天開始我們會逐一把前面說的demo分享出來,今天我們先來看下容器初始化組件——ApplicationContextInitializer。
自定義初始化組件
初始化組件
定義一個初始化組件,實(shí)現(xiàn)ApplicationContextInitializer,然后在initialize方法中寫入我們要進(jìn)行的初始化操作:
/**
* 自定義容器初始化類
*
* @author syske
* @version 1.0
* @date 2021-09-15 8:02
*/
public class SyskeInitializer implements ApplicationContextInitializer {
private Logger logger = LoggerFactory.getLogger(SyskeInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
logger.info("====================start======================");
logger.info("SyskeInitializer initialize方法被執(zhí)行……");
logger.info("ApplicationName: {}", configurableApplicationContext.getApplicationName());
logger.info("isActive: {}", String.valueOf(configurableApplicationContext.isActive()));
logger.info("====================end======================");
}
}
這樣我們的初始化組件就寫好了。
配置
在resources文件夾下創(chuàng)建META-INF文件夾,并在其下創(chuàng)建sping.factories文件,具體結(jié)構(gòu)如下:

然后在spring.factories文件中加入初始化組件的配置:
org.springframework.context.ApplicationContextInitializer=io.github.syske.springbootlearning.initializer.SyskeInitializer
這里的org.springframework.context.ApplicationContextInitializer就是我們前面實(shí)現(xiàn)的初始化接口,io.github.syske.springbootlearning.initializer.SyskeInitializer就是我們的初始化組件的具體實(shí)現(xiàn),加這個配置的作用有兩點(diǎn),一個就是讓spring boot能夠識別我們自己增加的組件,二一個就是聲明我們組件的類型是org.springframework.context.ApplicationContextInitializer,這樣spring boot在啟動的過程中就會按初始化組件進(jìn)行處理。
這種配置方式通常被稱作SPI機(jī)制(Service Provider Interface),在各類第三方框架中經(jīng)常出現(xiàn),比如dubbo、spring boot,通過這種方式既可以為框架提供各種擴(kuò)展,同時又可以降低框架與擴(kuò)展之間的依賴,可以實(shí)現(xiàn)有效解耦,確實(shí)很方便,當(dāng)然也很強(qiáng)大。
在spring boot中除了org.springframework.context.ApplicationContextInitializer之外,還為我們提供了如下擴(kuò)展:
org.springframework.boot.env.PropertySourceLoader:Property資源加載器org.springframework.boot.SpringApplicationRunListener:容器運(yùn)行監(jiān)聽器org.springframework.boot.SpringBootExceptionReporter:異常報告器org.springframework.context.ApplicationListener:應(yīng)用監(jiān)聽器org.springframework.boot.env.EnvironmentPostProcessor:環(huán)境變量后置處理器org.springframework.boot.diagnostics.FailureAnalyzer:失敗分析器org.springframework.boot.diagnostics.FailureAnalysisReporter:失敗分析報告器
當(dāng)然,以上這些可能補(bǔ)充的也不是很完整,這里僅供參考。用法上和我們今天演示的ApplicationContextInitializer基本上都是一樣的,各位小伙伴可以自行嘗試。
另外需要提一點(diǎn)的是,spring-boot的starter也是基于spring.factories來實(shí)現(xiàn)的,感興趣的小伙伴可以回顧下:
啟動測試
完成以上代碼編寫和配置工作以后,我們直接運(yùn)行啟動spring boot,然后會在控制臺輸出我們在initialize方法輸出的內(nèi)容:

從上面的啟動日志中,我們可以發(fā)現(xiàn),ApplicationContextInitializer組件是緊挨著banner打印被執(zhí)行的,結(jié)合我們最近分析的啟動過程,我們可以知道,初始化組件是在prepareContext方法中被執(zhí)行的,而這個方法是緊挨著容器創(chuàng)建的:

從setInitializer方法這里我們可以看到,我們自己定義的ApplicationContextInitalizer已經(jīng)被注冊到initializers中了

加載過程分析
具體的配置和用法分享完了,下面我們結(jié)合最近研究的spring boot啟動過程分析下spring.factories的加載過程。
spring.factories文件需要放在META-INF文件夾下,注意觀察文件名,我們可以發(fā)現(xiàn),文件后綴剛好是factory的復(fù)數(shù)形式,這也說明了這個文件的用途。它是在run方法中被解析的,更準(zhǔn)確的說是在實(shí)例化SpringApplication的時候被解析的:
首先我們在程序主入口中調(diào)用了SpringApplication的靜態(tài)方法run:

然后靜態(tài)run方法調(diào)用了另一個靜態(tài)run,并在第二個靜態(tài)run方法內(nèi)部實(shí)例化SpringApplication:

實(shí)例化SpringApplication的時候,會同步調(diào)用setInitializers(緊挨著的setListeners是設(shè)置應(yīng)用監(jiān)聽器的),這里設(shè)置的就是容器初始化組件(包括我們自定義的),這里獲取初始化組件的方法getSpringFactoriesInstances其內(nèi)部進(jìn)行了spring.factories文件的解析操作:

下面就是getSpringFactoriesInstances方法的內(nèi)部實(shí)現(xiàn):
其中的SpringFactoriesLoader.loadFactoryNames就是解析并獲取spring.factories文件中的Factory的名字,然后將最終結(jié)果放進(jìn)緩存中并返回:


總結(jié)
今天我們分享了自定義初始化組件,演示了定義過程、配置方式以及最終測試結(jié)果,整體來說整個過程還是比較簡單的,當(dāng)然重要的還是如何將這一組件和我們的具體業(yè)務(wù)相結(jié)合,實(shí)現(xiàn)我們具體的業(yè)務(wù),這應(yīng)該才是我們應(yīng)該思考的。好了,關(guān)于spring boot初始化組件我們暫時就分享這么多,有興趣的小伙伴可以親自動手試一下。
