一、ApplicationContextInitializer接口

用于ConfigurableApplicationContext通过调用refresh函数来初始化Spring容器之前的回调函数;

通常在web应用中,设计在初始化Spring容器之前调用。例如依赖于容器ConfigurableApplicationContext中的Enviroment来记录一些配置信息或者使一些配置文件生效;

参考ContextLoader和FrameworkServlet中支持定义contextInitializerClasses作为context-param或定义init-param。

支持Order注解,表示执行顺序,越小越早执行;

 

二、用法

定义一个ApplicationContextInitializer实现类。

public class DemoApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext context) {

        System.err.println("-----------------------------------------------" + this.getClass().getSimpleName());
    }
}




public class DemoApplicationContextInitializer2 implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext context) {

        System.err.println("-----------------------------------------------" + this.getClass().getSimpleName());
    }
}

1.手动添加

2.在项目resources目录下新建META-INF/spring.factories文件,在配置文件加入如下配置项org.springframework.context.ApplicationContextInitializer = com.springboot.helloworld.applicationInitalizer.SpringFactoryApplicationContextInitializer

参考spring-boot-2.1.6.RELEASE.jar中的配置;

3.在项目resources目录下新建application.properties文件,在配置文件中加入如下配置项(如果有多个,用,隔开)context.initializer.classes = com.springboot.helloworld.applicationInitalizer.ApplicationPropertiesApplicationContextInitializer

三、ApplicationContextInitializer分析

1.在手动添加的用法中,自定义的ApplicationContextInitalizer实例会存储到SpringApplication实例的initializers集合中,该集合的调用是如何触发的呢?

在SpringApplication类中的run方法,有如下相关代码:

 public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);  #刷新容器前,初始化
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

prepareContext方法的核心代码如下:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context); #遍历initializers集合中的SpringApplicationInitializer实例
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

SpringApplication中的applyInitializers方法就是遍历initializers集合中的SpringApplicationInitializer实例,调用其initialize方法。

protected void applyInitializers(ConfigurableApplicationContext context) {
        Iterator var2 = this.getInitializers().iterator();

        while(var2.hasNext()) {
            ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }

    }

综上,ApplicationContextInitializer的调用时机,是在容器刷新之前的prepareContext方法,通过获取ApplicationContextInitializer的集合,遍历调用initialize方法。

 

2.实例化SpringApplication对象使,其构造方法如下(对应用法2):

public SpringApplication(Class... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

其中会调用setInitializers方法设置SpringApplicationInitializer集合;

getSpringFactoriesInstances(ApplicationContextInitializer.class)逻辑如下:

(1)该方法会扫描所有的META-INF/spring.factorties文件

(2)获取key为org.springframework.context.ApplicationContextInitializer的所有属性值(本质上是ApplicationContextInitializer的实现类的类全名)

(3)然后使用其对应类的无参构造器进行反射实例化并进行排序,然后设置到SpringApplication实例中的initializers集合中;

所以我们创建spring.factories文件中的自定义ApplicationContextInitializer会加入到initializers集合中。

spring-boot-2.1.6.RELEASE.jar中的spring.factories文件定义的ApplicationContextInitializer实现如下:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

3.分析上面第2步中所说的spring-boot中自带的DelegatingApplicationContextInitializer类的initialize方法(对应用法3)

 public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        List<Class<?>> initializerClasses = this.getInitializerClasses(environment);
        if (!initializerClasses.isEmpty()) {
            this.applyInitializerClasses(context, initializerClasses);
        }

    }

从容器上下文中的Environment中获取配置名为context.initializer.classes的属性值(多个用,隔开,实际配置的是类全名),如果存在就通过反射实例化这些ApplicationContextInitialzier对象并排序,遍历并调用其initialize方法。

该类实际上是一个委托类,将实际的初始化工作交给了context.initializer.classes环境变量指定的ApplicationContextInitialize对象;

 

四、ApplicationContextInitializer执行顺序

springboot中自带的DelegatingApplicationContextInitializer类的排序值为0,是springboot自带的ApplicationContextInitializer中排序最小,最先执行的类。(如果ApplicationContextInitializer没有实现Orderd接口,那么其排序值默认是最大,最后执行)

所以可以得到其执行顺序如下

1.如果我们通过DelegatingApplicationContextInitializer委托来执行我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer的顺序一定是在系统自带的其他ApplicationContextInitializer之前执行。

2.如果我们通过SpringApplication实例对象调用addInitializers方法加入自定义的ApplicationContextInitializer,那么spring-boot自带的ApplicationContextInitializer会先按顺序执行,再执行我们手动添加的自定义ApplicationContextInitializer(按照添加顺序执行),最后执行spring-boot自带的其他ApplicationContextInializer

3.如果我们创建自己的spring.factories文件,添加配置加入我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer会和spring-boot自带的ApplicationContextInitializer放在一起进行排序执行。

 

实验结果如下:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐