• 【Spring】SpringBoot的扩展点之ApplicationContextInitializer


    简介

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

    package org.springframework.context;
    
    /**
     * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
     * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
     *
     * 

    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. * *

    {@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 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

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

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

    实现方式

    它有三种实现方式:

    1. 通过SPI机制实现,在resources/META-INF/spring.factories中定义如下内容:
      org.springframework.context.ApplicationContextInitializer=com.alone.spring.aop.demo.config.ContextInitializerTest
    /**
     * 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. application.yml中定义如下内容:
    context:
      initializer:
        classes: com.alone.spring.aop.demo.config.YmlApplicationContextInitializer
    
    • 1
    • 2
    • 3
    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 在启动类中进行注册:
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
        springApplication.addInitializers(new MainFlagApplicationContextInitializer());
        springApplication.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @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 加载结束");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    三者的加载顺序是:
    application.yml >spring.factories >启动类
    在这里插入图片描述

    源码分析

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

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    看到上面第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

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);
        applicationContext.addBeanFactoryPostProcessor(postProcessor);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    初始化了一个CachingMetadataReaderFactoryPostProcessor至容器中

    DelegatingApplicationContextInitializer

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        List<Class<?>> initializerClasses = getInitializerClasses(environment);
        if (!initializerClasses.isEmpty()) {
            applyInitializerClasses(context, initializerClasses);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    ContextIdApplicationContextInitializer

    @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";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    ConditionEvaluationReportLoggingListener

    @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注册了一个ConditionEvaluationReportListener

    RestartScopeInitializer

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
    }
    
    • 1
    • 2
    • 3
    • 4

    自动重启相关。

    ConfigurationWarningsApplicationContextInitializer

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
    }
    
    • 1
    • 2
    • 3
    • 4

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

    RSocketPortInfoApplicationContextInitializer

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.addApplicationListener(new Listener(applicationContext));
    }
    
    • 1
    • 2
    • 3
    • 4

    增加了一个监听器用于监听RSockerServer的端口是否正常。
    在这里插入图片描述

    ServerPortInfoApplicationContextInitializer

    /**
     * {@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}.
     * 

    * 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"}. *

    * 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); }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    参考资料

    1. SpringBoot系统初始化器使用及源码解析(ApplicationContextInitializer)
    2. 跟我一起阅读SpringBoot源码(九)——初始化执行器
    3. Springboot扩展点之ApplicationContextInitializer
  • 相关阅读:
    spark shuffle——shuffle管理
    Word修订内容批量标红
    14 WEB漏洞:SQL注入之类型及提交注入
    一、XSS加解密编码解码工具
    问题 A: 二叉排序树 - 文本输出
    【JavaEE基础与高级 第60章】Java中的反射获取成员方法Method、获取成员变量Field(下篇)
    linux CentOS7.6安装jenkins(小白版本)
    NICE-SLAM——论文简析
    Spark项目实战-卡口流量统计
    MYSQL 主从复制与读写分离
  • 原文地址:https://blog.csdn.net/qq_41279172/article/details/134521203