Spring ApplicationContextInitializer介绍和原理

一、 ApplicationContextInitializer 介绍

首先看spring官网的介绍:

img

翻译一下:

  • 用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。(剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法)
  • 通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
  • 可排序的(实现Ordered接口,或者添加@Order注解)

看完这段解释,为了讲解方便,我们先看自定义 ApplicationContextInitializer 的三种方式。再通过SpringBoot的源码,分析生效的时间以及实现的功能等。

二、三种实现方式

首先新建一个类 MyApplicationContextInitializer 并实现 ApplicationContextInitializer 接口。

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
     @Override
     public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("-----MyApplicationContextInitializer initialize-----");
     }
}

2.1、mian函数中添加

优雅的写一个SpringBoot的main方法

 @SpringBootApplication
 public class MySpringBootApplication {
     public static void main(String[] args) {
         SpringApplication application = new SpringApplication(MySpringBootApplication.class);
         application.addInitializers(new MyApplicationContextInitializer());
         application.run(args);
     }
 }

运行,查看控制台:生效了

img

2.2、配置文件中配置

context.initializer.classes=org.springframework.boot.demo.common.MyApplicationContextInitializer 

img

2.3、SpringBoot的SPI扩展—META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=org.springframework.boot.demo.common.MyApplicationContextInitializer

img

三、排序问题

如图所示改造一下mian方法。打一个断点,debug查看排序情况。

img

给 MyApplicationContextInitializer 加上Order注解:我们指定其拥有最高的排序级别。(越高越早执行)

@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyApplicationContextInitializer implements ApplicationContextInitializer{
     @Override
     public void initialize(ConfigurableApplicationContext applicationContext) {
         System.out.println("-----MyApplicationContextInitializer initialize-----");
     }
}

下面我们通过debug分别验证二章节中提到的三种方法排序是否都是可以的。

首先验证2.1章节中采用的main函数中添加:debug,断点处查看 application.getInitializers() 这行代码的结果可见,排序生效了。

img

然后再分别验证2.2和2.3章节中的方法。排序都是可以实现的。

然而当采用2.3中的SPI扩展的方式,排序指定 @Order(Ordered.LOWEST_PRECEDENCE) 排序并没有生效。当然采用实现Ordered接口的方式,排序验证结果都是一样的。

四、通过源码分析ApplicationContextInitializer何时被调用

debug差看上文中自定的 MyApplicationContextInitializer 的调用栈。

img

可见 ApplicationContextInitializer 在容器刷新前的准备阶段被调用。 refreshContext(context);

在SpringBoot的启动函数中, ApplicationContextInitializer

     public ConfigurableApplicationContext run(String... args) {
         //记录程序运行时间
         StopWatch stopWatch = new StopWatch();
         stopWatch.start();
         // ConfigurableApplicationContext Spring 的上下文
         ConfigurableApplicationContext context = null;
         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
         configureHeadlessProperty();
         //从META-INF/spring.factories中获取监听器
         //1、获取并启动监听器
         SpringApplicationRunListeners listeners = getRunListeners(args);
         listeners.starting();
         try {
             ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                     args);
             //2、构造容器环境
             ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
             //处理需要忽略的Bean
             configureIgnoreBeanInfo(environment);
             //打印banner
             Banner printedBanner = printBanner(environment);
             ///3、初始化容器
             context = createApplicationContext();
             //实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
             exceptionReporters = getSpringFactoriesInstances(
                     SpringBootExceptionReporter.class,
                     new Class[]{ConfigurableApplicationContext.class}, context);
            //4、刷新容器前的准备阶段
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
             //5、刷新容器
             refreshContext(context);
             //刷新容器后的扩展接口
             afterRefresh(context, applicationArguments);
             stopWatch.stop();
             if (this.logStartupInfo) {
                 new StartupInfoLogger(this.mainApplicationClass)
                         .logStarted(getApplicationLog(), stopWatch);
             }
             listeners.started(context);
             callRunners(context, applicationArguments);
         } catch (Throwable ex) {
             handleRunFailure(context, ex, exceptionReporters, listeners);
             throw new IllegalStateException(ex);
         }
 
         try {
             listeners.running(context);
         } catch (Throwable ex) {
             handleRunFailure(context, ex, exceptionReporters, null);
             throw new IllegalStateException(ex);
         }
         return context;
     }

然后看在 refreshContext(context); 具体是怎么被调用的。

 private void prepareContext(ConfigurableApplicationContext context,
                             ConfigurableEnvironment environment,
                             SpringApplicationRunListeners listeners,
                             ApplicationArguments applicationArguments, 
                             Banner printedBanner) {
     context.setEnvironment(environment);
     postProcessApplicationContext(context);
     applyInitializers(context);
     ...
 }

然后在 applyInitializers 中遍历调用每一个被加载的 ApplicationContextInitializer 的 initialize(context); 方法,并将 ConfigurableApplicationContext 的实例传递给 initialize 方法。

 protected void applyInitializers(ConfigurableApplicationContext context) {
     for (ApplicationContextInitializer initializer : getInitializers()) {
         Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                 initializer.getClass(), ApplicationContextInitializer.class);
         Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
         initializer.initialize(context);
     }
 } 

OK,到这里通过源码说明了 ApplicationContextInitializer 是何时及如何被调用的。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值