• spring扩展点之五:ApplicationContextInitializer实现与使用


    ApplicationContextInitializer是Spring框架原有的东西,这个类的主要作用就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfiurableApplicationContext的实例做进一步的设置和处理。
     
    ApplicationContextInitializer接口是在spring容器刷新之前执行的一个回调函数。是在ConfigurableApplicationContext#refresh() 之前调用(当spring框架内部执行 ConfigurableApplicationContext#refresh() 方法的时候或者在SpringBoot的run()执行时),作用是初始化Spring ConfigurableApplicationContext的回调接口。
     
    ApplicationContextInitializer是Spring框架原有的概念, 这个类的主要目的就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。

    通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活概要文件。

    • 参考ContextLoader和FrameworkServlet中支持定义contextInitializerClasses作为context-param或定义init-param。
    • ApplicationContextInitializer支持Order注解,表示执行顺序,越小越早执行;
    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    
        /**
         * Initialize the given application context.
         * @param applicationContext the application to configure
         */
        void initialize(C applicationContext);
    
    }

     一、使用分析

    该接口典型的应用场景是web应用中需要编程方式对应用上下文做初始化。比如,注册属性源(property sources)或者针对上下文的环境信息environment激活相应的profile。

    在一个Springboot应用中,classpath上会包含很多jar包,有些jar包需要在ConfigurableApplicationContext#refresh()调用之前对应用上下文做一些初始化动作,因此它们会提供自己的ApplicationContextInitializer实现类,然后放在自己的META-INF/spring.factories属性文件中,这样相应的ApplicationContextInitializer实现类就会被SpringApplication#initialize发现

         // SpringApplication#initialize方法,在其构造函数内执行,从而确保在其run方法之前完成
         private void initialize(Object[] sources) {
            if (sources != null && sources.length > 0) {
                this.sources.addAll(Arrays.asList(sources));
            }
            this.webEnvironment = deduceWebEnvironment();
            setInitializers((Collection) getSpringFactoriesInstances(
                    ApplicationContextInitializer.class));//   <===================
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
        }

     然后在应用上下文创建之后,应用上下文刷新(refresh)之前的准备阶段被调用 :

    SpringBoot内置的一些ApplicationContextInitializer

    下面列出了一个使用缺省配置的Springboot web应用默认所使用到的ApplicationContextInitializer实现们:

    DelegatingApplicationContextInitializer
    使用环境属性context.initializer.classes指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做。

    通过它使得我们可以把自定义实现类配置在application.properties里成为了可能。

    ContextIdApplicationContextInitializer
    设置Spring应用上下文的ID,会参照环境属性。至于Id设置为啥值会参考环境属性:
    spring.application.name
    vcap.application.name
    spring.config.name
    spring.application.index
    vcap.application.instance_index

    如果这些属性都没有,ID使用application。

    ConfigurationWarningsApplicationContextInitializer
    对于一般配置错误在日志中作出警告

    ServerPortInfoApplicationContextInitializer
    将内置servlet容器实际使用的监听端口写入到Environment环境属性中。这样属性local.server.port就可以直接通过@Value注入到测试中,或者通过环境属性Environment获取。

    SharedMetadataReaderFactoryContextInitializer
    创建一个SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory对象。实现类为:ConcurrentReferenceCachingMetadataReaderFactory

    ConditionEvaluationReportLoggingListener
    将ConditionEvaluationReport写入日志。

    以上都是SpringBoot内置的上文启动器,可见Spring留出的这个钩子,被SpringBoot发扬光大了。
    实际上不仅于此,SpringBoot对Spring Framework的事件监听机制也都有大量的应用~

    总结
    ApplicationContextInitializer是Spring留出来允许我们在上下文刷新之前做自定义操作的钩子,若我们有需求想要深度整合Spring上下文,借助它不乏是一个非常好的实现。

    随便浏览一下SpringBoot的源码可知,它对Spring特征特性的使用,均是非常的流畅且深度整合的。所以说SpringBoot易学难精的最大拦路虎:其实是对Spring Framework系统性的把握~

    Tips:spring-test包里有个注解org.springframework.test.context.ContextConfiguration它有个属性可以指定ApplicationContextInitializer辅助集成测试时候的自定义对上下文进行预处理~

    二、扩展实现方式

    2.1、编程方式

    先定义ApplicationContextInitializer,如下:

    package com.transsnet.palmpay.controller;
    
    import org.springframework.context.ApplicationContextInitializer;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.annotation.Order;
    
    //@Order的value值越小->越早执行。注:在类上标注,不是方法上
    @Order(111)
    public class ApplicationContextInitializer1 implements ApplicationContextInitializer {
    
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
    
            // 打印容器里面有多少个bean
            System.out.println("bean count=====" + applicationContext.getBeanDefinitionCount());
    
            // 打印人所有 beanName
            System.out.println(applicationContext.getBeanDefinitionCount() + "个Bean的名字如下:");
            String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
            for (String beanName : beanDefinitionNames) {
                System.out.println(beanName);
            }
    
        }
    }

    启动类里手动增加initializer

    @SpringBootApplication
    @EnableConfigServer
    @EnableDiscoveryClient
    public class ConfigServer {
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(ConfigServer.class);
    
            // 方法一:添加自定义的 ApplicationContextInitializer 实现类的实例(注册ApplicationContextInitializer)
            springApplication.addInitializers(new ApplicationContextInitializer1());
    
            ConfigurableApplicationContext context = springApplication.run(args);
    
            context.close();
        }
    }

    结果:

    2.2、application.properties添加配置方式

    对于这种方式是通过DelegatingApplicationContextInitializer这个初始化类中的initialize方法获取到application.properties中context.initializer.classes对应的类并执行对应的initialize方法。只需要将实现了ApplicationContextInitializer的类添加到application.properties即可。如下:

    1、先定义先定义ApplicationContextInitializer,同1.1

    2、在application.properties中定义:

    2.3、使用spring.factories方式

    1、先定义先定义ApplicationContextInitializer,同1.1

    2、然后在项目下的resources下新建META-INF文件夹,文件夹下新建spring.factories文件

    org.springframework.context.ApplicationContextInitializer=com.dxz.ApplicationContextInitializer1

    这个加载过程是在SpringApplication中的getSpringFactoriesInstances()方法中直接加载并实例后执行对应的initialize方法。代码如下:

        private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
            return getSpringFactoriesInstances(type, new Class<?>[] {});
        }
    
        private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
                Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // Use names and ensure unique to protect against duplicates
            Set<String> names = new LinkedHashSet<String>(
                    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                    classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }

    三、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放在一起进行排序执行。

  • 相关阅读:
    innobackupex备份命令输出
    Percona XtraBackup原理详解
    MongoDB性能分析工具mongostat
    MongoDB查看当前连接数
    事务、拦截器
    HttpServletResponse和HttpServletRequest的简单实用
    Maven环境配置
    SQL Server 时间戳与时间格式互相转换
    虚拟机、云主机、VPS 三者之间的区别
    Elasticsearch 空值过滤
  • 原文地址:https://www.cnblogs.com/duanxz/p/11239291.html
Copyright © 2020-2023  润新知