SpringBoot之ApplicationContextInitializer

一、 ApplicationContextInitializer 介绍

ApplicationContextInitializer是Spring框架提供的一个接口,用于在Spring上下文(ApplicationContext)创建之前对其进行初始化操作。它可以让您通过编程方式修改或设置应用程序上下文的属性、环境、配置源等内容。

当Spring应用程序启动时,会触发Spring上下文的创建和初始化过程。在这个过程中,Spring会尝试寻找并执行所有实现了ApplicationContextInitializer接口的类的initialize方法。通过实现此接口,并在initialize方法中编写自定义逻辑,您可以在Spring上下文创建之前对其进行定制化操作。

看看spring官网的介绍:

翻译一下:

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

二、三种实现方式

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

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

2.1、mian函数中添加
 

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

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

 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注解:我们指定其拥有最高的排序级别。(越高越早执行)

@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() 这行代码的结果可见,排序生效了。

  

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

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

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

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

可见 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 是何时及如何被调用的。

挖井人在此:SpringBoot之ApplicationContextInitializer的理解和使用 - 超级小小黑 - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值