SpringBoot之ApplicationContextInitializer的理解和使用

一、 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 是何時及如何被調用的。

 

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

您可能也會喜歡…