【Spring】SpringBoot的扩展点之ApplicationContextInitializer

简介

其实spring启动步骤中最早可以进行扩展的是实现ApplicationContextInitializer接口。来看看这个接口的注释。

java 复制代码
package org.springframework.context;

/**
 * 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
 */
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

简要的说明一下,有这么几点:

  1. 实现这个接口之后,它的initialize方法会在容器ConfigurableApplicationContext刷新之前触发。
  2. 它通常用于在容器初始化之前进行一些程序上的操作,比如说注册一些环境变量或者读取一些配置文件。
  3. 它可以使用@Order指定优先级

实现方式

它有三种实现方式:

  1. 通过SPI机制实现,在resources/META-INF/spring.factories中定义如下内容:
    org.springframework.context.ApplicationContextInitializer=com.alone.spring.aop.demo.config.ContextInitializerTest
java 复制代码
/**
 * spring扩展点 ApplicationContextInitializer
 */
@Slf4j
public class ContextInitializerTest implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("ContextInitializerTest 开始加载");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "This is init");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("ContextInitializerTest 加载结束");
    }
}
  1. application.yml中定义如下内容:
yml 复制代码
context:
  initializer:
    classes: com.alone.spring.aop.demo.config.YmlApplicationContextInitializer
java 复制代码
@Slf4j
public class YmlApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("这是yml的ApplicationContextInitializer");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "YmlApplicationContextInitializer");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("YmlApplicationContextInitializer 加载结束");
    }
}
  1. 在启动类中进行注册:
java 复制代码
public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
    springApplication.addInitializers(new MainFlagApplicationContextInitializer());
    springApplication.run();
}
java 复制代码
@Component
@Slf4j
public class MainFlagApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("这是main的ApplicationContextInitializer");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "MainFlagApplicationContextInitializer");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("MainFlagApplicationContextInitializer 加载结束");
    }
}

三者的加载顺序是:
application.yml >spring.factories >启动类

源码分析

从启动类的new SpringApplication(SpringbootApplication.class)开始分析:

java 复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

看到上面第8行(源码266行)中出现了ApplicationContextInitializer.class猜想它肯定是在读取相关的配置,跟进去发现出现了下面这行。

这里是读取了spring.factories中的内容,但看它的结果发现不止我们自定义的类一个,说明springboot内置了一些ApplicationContextInitializer,后续我们再看它们具体的作用,这里先截图列出按下不表。

然后沿如下的调用栈可以找到initializer.initialize(context);这一行调用ApplicationContextInitializer的语句。

springApplication.run()

run:306, SpringApplication (org.springframework.boot)

prepareContext:383, SpringApplication (org.springframework.boot)

applyInitializers:614, SpringApplication (org.springframework.boot)

框起来的方法会对所有的initializer进行排序,排序后的结果见左边。

在执行到DelegatingApplicationContextInitializer时会去读取环境中的context.initializer.classes,也就是application.yml中配置的内容执行。所以会先执行yml配置的initializer.

以上总结一下是这样的:

大致调用的流程图是:

系统内置初始化类

最后我们来看看上面提到的系统内置的初始化类都有些什么作用。

SharedMetadataReaderFactoryContextInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);
    applicationContext.addBeanFactoryPostProcessor(postProcessor);
}

初始化了一个CachingMetadataReaderFactoryPostProcessor至容器中

DelegatingApplicationContextInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();
    List<Class<?>> initializerClasses = getInitializerClasses(environment);
    if (!initializerClasses.isEmpty()) {
        applyInitializerClasses(context, initializerClasses);
    }
}

执行context.initializer.classes配置的initializer。

ContextIdApplicationContextInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    ContextId contextId = getContextId(applicationContext);
    applicationContext.setId(contextId.getId());
    applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}

private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
    ApplicationContext parent = applicationContext.getParent();
    if (parent != null && parent.containsBean(ContextId.class.getName())) {
        return parent.getBean(ContextId.class).createChildId();
    }
    return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

private String getApplicationId(ConfigurableEnvironment environment) {
    String name = environment.getProperty("spring.application.name");
    return StringUtils.hasText(name) ? name : "application";
}

设置容器的id,值取自spring.application.name配置,默认是application

ConditionEvaluationReportLoggingListener

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    applicationContext.addApplicationListener(new ConditionEvaluationReportListener());
    if (applicationContext instanceof GenericApplicationContext) {
        // Get the report early in case the context fails to load
        this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
    }
}

注册了一个ConditionEvaluationReportListener

RestartScopeInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
}

自动重启相关。

ConfigurationWarningsApplicationContextInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}

初始化一个ConfigurationWarningsPostProcessor用于记录公共的容器配置错误信息。

RSocketPortInfoApplicationContextInitializer

java 复制代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.addApplicationListener(new Listener(applicationContext));
}

增加了一个监听器用于监听RSockerServer的端口是否正常。

ServerPortInfoApplicationContextInitializer

java 复制代码
/**
 * {@link ApplicationContextInitializer} that sets {@link Environment} properties for the
 * ports that {@link WebServer} servers are actually listening on. The property
 * {@literal "local.server.port"} can be injected directly into tests using
 * {@link Value @Value} or obtained via the {@link Environment}.
 * <p>
 * If the {@link WebServerInitializedEvent} has a
 * {@link WebServerApplicationContext#getServerNamespace() server namespace} , it will be
 * used to construct the property name. For example, the "management" actuator context
 * will have the property name {@literal "local.management.port"}.
 * <p>
 * Properties are automatically propagated up to any parent context.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @since 2.0.0
 */

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.addApplicationListener(this);
}

向容器中增加一个监听器用于检测WebServer的端口是否正常监听。

参考资料

  1. SpringBoot系统初始化器使用及源码解析(ApplicationContextInitializer)
  2. 跟我一起阅读SpringBoot源码(九)------初始化执行器
  3. Springboot扩展点之ApplicationContextInitializer
相关推荐
顾北川_野6 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航8 分钟前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself24 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041529 分钟前
J2EE平台
java·java-ee
XiaoLeisj35 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
鹿屿二向箔1 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的汽车租赁共享平台系统
spring·mvc·mybatis
豪宇刘1 小时前
SpringBoot+Shiro权限管理
java·spring boot·spring
Elaine2023911 小时前
02多线程基础知识
java·多线程
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试