什么是ApplicationContextInitializer?作用是什么?
注释中描述ApplicationContextInitializer主要在ConfigurableApplicationContext的refresh方法中作为callback作用(spring boot应用在启动时,会在prepareContext时调用ApplicationContextInitializer的initialize方法),通常用在需要对某些参数进行初始化的web应用中,并鼓励在ApplicationContextInitializer实现类中使用@Order注解或者实现Ordered接口。
下图是spring boot中所有ApplicationContextInitializer实现类(剔除了test包中的实现类)。下面我们对这些实现类挨个解释说明
ContextIdApplicationContextInitializer
private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";
private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}";
public ContextIdApplicationContextInitializer() {
this(NAME_PATTERN);
}
public ContextIdApplicationContextInitializer(String name) {
this.name = name;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
}
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.resolvePlaceholders(this.name);
String index = environment.resolvePlaceholders(INDEX_PATTERN);
String profiles = StringUtils
.arrayToCommaDelimitedString(environment.getActiveProfiles());
if (StringUtils.hasText(profiles)) {
name = name + ":" + profiles;
}
if (!"null".equals(index)) {
name = name + ":" + index;
}
return name;
}
代码比较简单。依次获取应用的name,index,profiles,然后对三个值进行拼接。获取name的逻辑,首先读取spring.application.name属性,如果没有value,则读取vcap.application.name属性,如果没有value,再读取spring.config.name:application属性。index的读取规则同name的一致。profiles如果有多个值,则使用逗号分割拼接。
ServerPortInfoApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(
new ApplicationListener<EmbeddedServletContainerInitializedEvent>() {
@Override
public void onApplicationEvent(
EmbeddedServletContainerInitializedEvent event) {
ServerPortInfoApplicationContextInitializer.this
.onApplicationEvent(event);
}
});
}
protected void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
String propertyName = getPropertyName(event.getApplicationContext());
setPortProperty(event.getApplicationContext(), propertyName,
event.getEmbeddedServletContainer().getPort());
}
protected String getPropertyName(EmbeddedWebApplicationContext context) {
String name = context.getNamespace();
if (StringUtils.isEmpty(name)) {
name = "server";
}
return "local." + name + ".port";
}
private void setPortProperty(ApplicationContext context, String propertyName,
int port) {
if (context instanceof ConfigurableApplicationContext) {
setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(),
propertyName, port);
}
if (context.getParent() != null) {
setPortProperty(context.getParent(), propertyName, port);
initialize方法是向context中添加ApplicationListener(又来一个没见过的东西,什么是ApplicationListener?点这里)。Listener的onApplicationEvent方法就是ServerPortInfoApplicationContextInitializer的onApplicationEvent方法。方法逻辑大致在不配置context的命名空间的情况下,获取配置文件中的local.server.port中的value,然后将port的值设置到context中。
ParentContextApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext != this.parent) {
applicationContext.setParent(this.parent);
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
}
设置context的父context,并设置事件发布者
ServletContextApplicationContextInitializer
@Override
public void initialize(ConfigurableWebApplicationContext applicationContext) {
applicationContext.setServletContext(this.servletContext);
if (this.addApplicationContextAttribute) {
this.servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
applicationContext);
}
}
设置上下文的servletContext属性
SharedMetadataReaderFactoryContextInitializer
ConfigurationWarningsApplicationContextInitializer
@Override
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(
new ConfigurationWarningsPostProcessor(getChecks()));
}
/**
* Returns the checks that should be applied.
* @return the checks to apply
*/
protected Check[] getChecks() {
return new Check[] { new ComponentScanPackageCheck() };
}
ConfigurationWarningsApplicationContextInitializer的作用是用来报告Spring容器的一些常见的错误配置的。这里initialize方法中向context中添加 校验 bean扫描路径是否合规 BeanFactoryPostProcessor(不懂BeanFactoryPostProcessor得同学可以自行百度下,这里不做过多描述)。
AutoConfigurationReportLoggingInitializer
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
applicationContext.addApplicationListener(new AutoConfigurationReportListener());
if (applicationContext instanceof GenericApplicationContext) {
// Get the report early in case the context fails to load
this.report = ConditionEvaluationReport
.get(this.applicationContext.getBeanFactory());
}
}
往context中添加AutoConfigurationReportListener监听器(支持监听上下文刷新事件、应用失败事件)。
DelegatingApplicationContextInitializer
private static final String PROPERTY_NAME = "context.initializer.classes";
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<Class<?>>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
classes.add(getInitializerClass(className));
}
}
return classes;
}
从properties文件中获取context.initializer.classes的属性值并按逗号进行分割,然后初始化得到的initializer类实例,并按照order进行排序,最后依次调用其initialize方法。
了解了每个springboot自带的ApplicationContextInitializer后,下面介绍下如何创建自己的ApplicationContextInitializer。
1、spring.factories方式
首先resource下面新建/META-INF/spring.factories文件,然后添加
org.springframework.context.ApplicationContextInitializer=com.aihai.demo.MyApplicationContextInitializer
package com.aihai.demo;
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("自定义ApplicationContextInitializer");
}
}
2、application.properties方式
application.properties文件中增加
context.initializer.classes = com.aihai.demo.MyApplicationContextInitializer
3、启动类中直接添加Initializers方式
@SpringBootApplication
public class SpringMain{
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringMain.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
springApplication.run(args);
}