本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

简介

ApplicationContextInitializer 是啥呢,我们来看一下源码注释中的介绍:

 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 * 它是 Spring 初始化过程中的一个回调接口,会在 ConfigurableApplicationContext 的 refresh 方法执行前调用

所以我们可以在 SpringBoot 的这个回调点,去对 ApplicationContext 做一些事情 =。=
我们有三种方法让 SpringBoot 去加载它的实现:

0) 定义初始化器

我们可以实现 ApplicationContextInitalizer 来定义我们的初始化器,先来看一哈这个接口源码:

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);
}

实现它需要一个泛型,可以传入 ConfigurableApplicationContext 的实现,我们可以根据需要选择对应的实现,一般直接用它本身即可。
且可以使用 @Order 调整加载的顺序,这里仅演示注入一个 Environment:

@Order(1)
public class InjectInitializer
    implements ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    Map<String, Object> propertyMap = Map.of("InjectKey", "InjectValue");
    MapPropertySource propertySource = new MapPropertySource("InjectProperty", propertyMap);
    applicationContext.getEnvironment().getPropertySources().addLast(propertySource);
  }
}

1) 定义 /META-INF/spring.factories 注入

如果翻阅 SpringBoot 源码会得知启动过程中会从 /META-INF/spring.factories 中取得加载器并执行
spring-boot.jarspring-boot-autoconfigure.jar 中有此配置文件例子,我们可以参考它们的格式编写我们的 spring.factories
,放进 classpath 下的 META-INF 中:

# Application Context Initializers, 需添加多个可用 ',' 分隔
org.springframework.context.ApplicationContextInitializer=\
cat.wars.inject.InjectInitializer,\
cat.wars.inject.Inject2Initializer

2) 通过 SpringApplication 注入

通常我们的 SpringBoot 启动主函数是这样写的:

SpringApplication.run(Application.class, args);

可以拆分为两行:

SpringApplication application = new SpringApplication(Application.class);
application.run(args);

可以使用 SpringApplication.addInitializers 方法添加我们的初始化器

SpringApplication application = new SpringApplication(Application.class);
application.addInitializers(new InjectInitializer());
application.run(args);

3) 配置文件注入

spring-boot.jar 中的 spring.factories 中定义加载了 DelegatingApplicationContextInitializer
,其中会读取配置文件中 context.initializer.classes 属性,所以我们还能通过定义配置文件注入。

context:
  initializer:
    classes: cat.wars.course.multidb.initial.InjectInitializer,cat.wars.course.multidb.initial.Inject2Initializer

4) 总结

SpringBoot 中至少有三种方法,可以注入我们定义的 Spring 上下文初始化器,分别是:

  • 定义 /META-INF/spring.factories 注入
  • 通过 SpringApplicationaddInitializers 方法注入
  • 通过配置文件中 context.initializer.classes 属性注入