Spring中的ApplicationContextInitializer

Spring中的ApplicationContextInitializer

1.介绍

  • 先来看下这个类是用来做什么的。ApplicationContextInitializer类在源码中的注释如下:(源代码基于版本2.3.3)
* 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
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

这个类的主要目的就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。
通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活概要文件。

  • 参考ContextLoader和FrameworkServlet中支持定义contextInitializerClasses作为context-param或定义init-param。
  • ApplicationContextInitializer支持Order注解,表示执行顺序,越小越早执行;

2. 常见实现类分析

  • 使用分析:在从SpringBoot启动过程分析到自定义一个springboot-starter一文中我们分析到了在SpringApplication创建时先获取了一些ApplicationContextInitializer,然后是在prepareContext()应用上下文环境准备时使用这些初始化器做了具体的初始化操作。
  • 缺省配置的ApplicationContextInitializer配置类如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

逐个看一下每一个Initializer中都具体做了哪些初始化的操作:

  • ConfigurationWarningsApplicationContextInitializer:对于一般配置错误在日志中作出警告
  • ContextIdApplicationContextInitializer:设置ApplicationContext#getId()所获取的ID值,默认取spring.application.name属性值,没有配置时默认为 application
  • DelegatingApplicationContextInitializer:使用环境属性context.initializer.classes指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做
    /**
     * {@link ApplicationContextInitializer} that delegates to other initializers that are
     * specified under a {@literal context.initializer.classes} environment property.
     *
     * @author Dave Syer
     * @author Phillip Webb
     */
    public class DelegatingApplicationContextInitializer
           implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
    
       // NOTE: Similar to org.springframework.web.context.ContextLoader
    
       private static final String PROPERTY_NAME = "context.initializer.classes";
    
       private int order = 0;
    
       @Override
       public void initialize(ConfigurableApplicationContext context) {
           ConfigurableEnvironment environment = context.getEnvironment();
           List<Class<?>> initializerClasses = getInitializerClasses(environment);
           if (!initializerClasses.isEmpty()) {
               applyInitializerClasses(context, initializerClasses);
           }
       }
    
    从上面的部分代码可见,实现了Ordered接口时,该initializer的order为0,也就是说使用DelegatingApplicationContextInitializer方式进行包装配置的初始化器将会最先被加载
  • ServerPortInfoApplicationContextInitializer: 将内置servlet容器实际使用的监听端口写入到Environment环境属性中。这样属性local.server.port就可以直接通过@Value注入到测试中,或者通过环境属性Environment获取

3. ApplicationContextInitializer的使用

Spring通过ApplicationContextInitializer允许我们在SpringApplication上下文刷新之前进行自定义的操作,可以使用这个hook来实现某些特定的需求。

  • 首先定义一个ApplicationContextInitializer,可以使用实现Ordered接口或@Order注解的方式指定顺序:
 /**
  * 自定义的ApplicationContextInitializer
  *
  * @author duwenxu
  * @create 2021-01-25 13:48
  */
 @Slf4j
 public class MyInitializer implements ApplicationContextInitializer,Ordered {
     public int getOrder() {
         return 111;
     }
 
     public void initialize(ConfigurableApplicationContext applicationContext) {
         ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
         Iterator<String> beanNamesIterator = beanFactory.getBeanNamesIterator();
         int beanDefinitionCount = beanFactory.getBeanDefinitionCount();
         log.info("beanDefinitionCount={}",beanDefinitionCount);
         while (beanNamesIterator.hasNext()){
             System.out.println(beanNamesIterator.next());
         }
     }
 }

ApplicationContextInitializer的使用有以下3中方式:

  • 在启动类中使用SpringApplication.addInitializers() 手动增加Initializer
@SpringBootApplication
@Slf4j
public class WebsocketApplication {

 public static void main(String[] args) {
     run(args);
 }

 private static void run(String[] args) {
     SpringApplication springApplication = new SpringApplication(WebsocketApplication.class);
     springApplication.addInitializers(new MyInitializer());
     springApplication.run(args);
 }
}
  • application.properties添加配置方式

    在分析缺省的initializer时,提到了DelegatingApplicationContextInitializer,可以使用该类来添加我们自己的initializers

    添加如下的配置properties中即可:
 context.initializer.classes=com.netty.websocket.initializertest.MyInitializer
  • 可以参照SpringBoot缺省的Initializer,将MyInitializer配置在spring.factories文件中
 org.springframework.context.ApplicationContextInitializer=com.netty.websocket.initializertest.MyInitializer

spring在获取ApplicationContextInitializer时将会获取到我们自定义的MyInitializer

4. 上述几种方式ApplicationContextInitializer的执行顺序

  1. 如果我们通过DelegatingApplicationContextInitializer委托来执行我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer的顺序一定是在系统自带的其他ApplicationContextInitializer之前执行。
  2. 如果我们通过SpringApplication实例对象调用addInitializers方法加入自定义的ApplicationContextInitializer,那么spring-boot自带的ApplicationContextInitializer会先按顺序执行,再执行我们手动添加的自定义ApplicationContextInitializer(按照添加顺序执行),最后执行spring-boot自带的其他ApplicationContextInializer
  3. 如果我们创建自己的spring.factories文件,添加配置加入我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer会和spring-boot自带的ApplicationContextInitializer放在一起进行排序执行
    执行结果如下:


    加载自定义的ApplicationContextInitializer打印信息.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容