一、 ApplicationContextInitializer 介紹
首先看spring官網的介紹:
翻譯一下:
- 用於在spring容器重新整理之前初始化Spring ConfigurableApplicationContext的回撥介面。(剪短說就是在容器重新整理之前呼叫該類的 initialize 方法。並將 ConfigurableApplicationContext 類的例項傳遞給該方法)
- 通常用於需要對應用程式上下文進行程式設計初始化的web應用程式中。例如,根據上下文環境註冊屬性源或啟用配置檔案等。
- 可排序的(實現Ordered介面,或者新增@Order註解)
看完這段解釋,為了講解方便,我們先看自定義 ApplicationContextInitializer 的三種方式。再通過SpringBoot的原始碼,分析生效的時間以及實現的功能等。
二、三種實現方式
首先新建一個類 MyApplicationContextInitializer 並實現 ApplicationContextInitializer 介面。
1 public class MyApplicationContextInitializer implements ApplicationContextInitializer { 2 @Override 3 public void initialize(ConfigurableApplicationContext applicationContext) { 4 System.out.println("-----MyApplicationContextInitializer initialize-----"); 5 } 6 }
2.1、mian函式中新增
優雅的寫一個SpringBoot的main方法
1 @SpringBootApplication 2 public class MySpringBootApplication { 3 public static void main(String[] args) { 4 SpringApplication application = new SpringApplication(MySpringBootApplication.class); 5 application.addInitializers(new MyApplicationContextInitializer()); 6 application.run(args); 7 } 8 }
執行,檢視控制檯:生效了
2.2、配置檔案中配置
context.initializer.classes=org.springframework.boot.demo.common.MyApplicationContextInitializer
2.3、SpringBoot的SPI擴充套件---META-INF/spring.factories中配置
org.springframework.context.ApplicationContextInitializer=org.springframework.boot.demo.common.MyApplicationContextInitializer
三、排序問題
如圖所示改造一下mian方法。打一個斷點,debug檢視排序情況。
給 MyApplicationContextInitializer 加上Order註解:我們指定其擁有最高的排序級別。(越高越早執行)
1 @Order(Ordered.HIGHEST_PRECEDENCE) 2 public class MyApplicationContextInitializer implements ApplicationContextInitializer{ 3 @Override 4 public void initialize(ConfigurableApplicationContext applicationContext) { 5 System.out.println("-----MyApplicationContextInitializer initialize-----"); 6 } 7 }
下面我們通過debug分別驗證二章節中提到的三種方法排序是否都是可以的。
首先驗證2.1章節中採用的main函式中新增:debug,斷點處檢視 application.getInitializers() 這行程式碼的結果可見,排序生效了。
然後再分別驗證2.2和2.3章節中的方法。排序都是可以實現的。
然而當採用2.3中的SPI擴充套件的方式,排序指定 @Order(Ordered.LOWEST_PRECEDENCE) 排序並沒有生效。當然採用實現Ordered介面的方式,排序驗證結果都是一樣的。
四、通過原始碼分析ApplicationContextInitializer何時被呼叫
debug差看上文中自定的 MyApplicationContextInitializer 的呼叫棧。
可見 ApplicationContextInitializer 在容器重新整理前的準備階段被呼叫。 refreshContext(context);
在SpringBoot的啟動函式中, ApplicationContextInitializer
1 public ConfigurableApplicationContext run(String... args) { 2 //記錄程式執行時間 3 StopWatch stopWatch = new StopWatch(); 4 stopWatch.start(); 5 // ConfigurableApplicationContext Spring 的上下文 6 ConfigurableApplicationContext context = null; 7 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 8 configureHeadlessProperty(); 9 //從META-INF/spring.factories中獲取監聽器 10 //1、獲取並啟動監聽器 11 SpringApplicationRunListeners listeners = getRunListeners(args); 12 listeners.starting(); 13 try { 14 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 15 args); 16 //2、構造容器環境 17 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 18 //處理需要忽略的Bean 19 configureIgnoreBeanInfo(environment); 20 //列印banner 21 Banner printedBanner = printBanner(environment); 22 ///3、初始化容器 23 context = createApplicationContext(); 24 //例項化SpringBootExceptionReporter.class,用來支援報告關於啟動的錯誤 25 exceptionReporters = getSpringFactoriesInstances( 26 SpringBootExceptionReporter.class, 27 new Class[]{ConfigurableApplicationContext.class}, context); 28 //4、重新整理容器前的準備階段 29 prepareContext(context, environment, listeners, applicationArguments, printedBanner); 30 //5、重新整理容器 31 refreshContext(context); 32 //重新整理容器後的擴充套件介面 33 afterRefresh(context, applicationArguments); 34 stopWatch.stop(); 35 if (this.logStartupInfo) { 36 new StartupInfoLogger(this.mainApplicationClass) 37 .logStarted(getApplicationLog(), stopWatch); 38 } 39 listeners.started(context); 40 callRunners(context, applicationArguments); 41 } catch (Throwable ex) { 42 handleRunFailure(context, ex, exceptionReporters, listeners); 43 throw new IllegalStateException(ex); 44 } 45 46 try { 47 listeners.running(context); 48 } catch (Throwable ex) { 49 handleRunFailure(context, ex, exceptionReporters, null); 50 throw new IllegalStateException(ex); 51 } 52 return context; 53 }
然後看在 refreshContext(context); 具體是怎麼被呼叫的。
1 private void prepareContext(ConfigurableApplicationContext context, 2 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments, Banner printedBanner) { 4 context.setEnvironment(environment); 5 postProcessApplicationContext(context); 6 applyInitializers(context); 7 ... 8 }
然後在 applyInitializers 中遍歷呼叫每一個被載入的 ApplicationContextInitializer 的 initialize(context); 方法,並將 ConfigurableApplicationContext 的例項傳遞給 initialize 方法。
1 protected void applyInitializers(ConfigurableApplicationContext context) { 2 for (ApplicationContextInitializer initializer : getInitializers()) { 3 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument( 4 initializer.getClass(), ApplicationContextInitializer.class); 5 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); 6 initializer.initialize(context); 7 } 8 }
OK,到這裡通過原始碼說明了 ApplicationContextInitializer 是何時及如何被呼叫的。