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)进行初始化工作,如果没有指定则什么都不做
从上面的部分代码可见,实现了Ordered接口时,该initializer的order为0,也就是说使用DelegatingApplicationContextInitializer方式进行包装配置的初始化器将会最先被加载/** * {@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); } }
- 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的执行顺序
- 如果我们通过DelegatingApplicationContextInitializer委托来执行我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer的顺序一定是在系统自带的其他ApplicationContextInitializer之前执行。
- 如果我们通过SpringApplication实例对象调用addInitializers方法加入自定义的ApplicationContextInitializer,那么spring-boot自带的ApplicationContextInitializer会先按顺序执行,再执行我们手动添加的自定义ApplicationContextInitializer(按照添加顺序执行),最后执行spring-boot自带的其他ApplicationContextInializer
-
如果我们创建自己的spring.factories文件,添加配置加入我们自定义的ApplicationContextInitializer,那么我们自定义的ApplicationContextInitializer会和spring-boot自带的ApplicationContextInitializer放在一起进行排序执行
执行结果如下: