Spring 之 ApplicationContextInitializer
Spring 之 ApplicationContextInitializer
1、简介
在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法会被调用,initialize方法的形参类型是ConfigurableApplicationContext,因此可以认为ApplicationContextInitializer实际上是Spring容器初始化前ConfigurableApplicationContext的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等。 ApplicationContextInitializer 接口在 SpringBoot 广泛使用。
通常自定义的ApplicationContextInitializer有三种注册方式,按照优先级如下:
配置文件 > SPI方式 > 启动方式注册 (相同的注册方式中,可以使用@Order注解来指定优先级,值越小优先级越高)。
2、接口定义如下:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
3、使用示例
1、实现ApplicationContextInitializer接口
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Map<String, Object> systemProperties = applicationContext.getEnvironment().getSystemProperties();
System.out.println("---------------start------------------");
systemProperties.forEach((key,value)->{
System.out.println("key:"+key+",value:"+value);
}); System.out.println("---------------end------------------");
}
}
2、把实现类注册到Spring容器中
把实现类MyApplicationContextInitializer注册到Spring容器中,主要有三种方式:
2.1、 spring.factories
在resources目录新建/META-INFI/spring.factories文件,并预置以下内容,即可完成自定义MyApplicationContextInitializer的注册;
org.springframework.context.ApplicationContextInitializer=com.dw.config.MyApplicationContextInitializer
2.2 、application.properties
在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册
context.initializer.classes=com.dw.config.MyApplicationContextInitializer
2.3、 springApplication.addInitializers()
在springboot的启动类中,使用springApplication.addInitializers(new MyApplicationContextInitializer()),完成自定义MyApplicationContextInitializer的注册。
@SpringBootApplication
public class FanfuApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(FanfuApplication.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
springApplication.run(args);
}
}
4、初始化时机
ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中,即spring容器初始化开始前,先通过 getSpringFactoriesInstances(...)得到所有实现类的集合,然后通过 setInitializers(...)注入到SpringApplication类的initializersn属性中;
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();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
5、执行时机
ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext-->org.springframework.boot.SpringApplication#applyInitializers中,即spring容器正式刷新前,准备上下文环境时;
getInitializers()得到,SpringApplication类的构造函数中通过 setInitializers(...)注入到SpringApplication类的initializersn属性中的所有ApplicationContextInitializer接口的实现类;
遍历这些实现类,并调用initialize()方法;
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
6、内置的实现类
Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现,常见的实现类如下:
6.1 DelegatingApplicationContextInitializer
ApplicationContextInitializer的第二种实现方式(application.properties),在application.properties文件中配置context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer,DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = this.getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
this.applyInitializerClasses(context, initializerClasses);
}
}
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
String classNames = env.getProperty("context.initializer.classes");
List<Class<?>> classes = new ArrayList();
if (StringUtils.hasLength(classNames)) {
String[] var4 = StringUtils.tokenizeToStringArray(classNames, ",");
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String className = var4[var6];
classes.add(this.getInitializerClass(className));
}
}
return classes;
}
6.2 ContextIdApplicationContextInitializer
ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为“application”
private ContextId getContextId(ConfigurableApplicationContext applicationContext) { ApplicationContext parent = applicationContext.getParent(); return parent != null && parent.containsBean(ContextId.class.getName()) ? ((ContextId)parent.getBean(ContextId.class)).createChildId() :
new ContextId(this.getApplicationId(applicationContext.getEnvironment())); } private String getApplicationId(ConfigurableEnvironment environment) { String name = environment.getProperty("spring.application.name"); return StringUtils.hasText(name) ? name : "application"; }
6.3、SharedMetadataReaderFactoryContextInitializer
实例化了一个org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor对象并注册到了Spring的上下文环境中,CachingMetadataReaderFactoryPostProcessor是SharedMetadataReaderFactoryContextInitializer的一个内部类,这里就不具体展开了,有兴趣的小伙伴可以继续跟一下org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor#postProcessBeanDefinitionRegistry方法了解一下具体的内容;
class SharedMetadataReaderFactoryContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."
+ "internalCachingMetadataReaderFactory";
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);
applicationContext.addBeanFactoryPostProcessor(postProcessor);
}
@Override
public int getOrder() {
return 0;
}
}
7、应用场景
通过它来指定需要激活的配置文件。 比如现在广为流行的docker容器部署,当我们希望每次都是打同一个镜像,然后在实际运行的时候,根据不同的环境来决定当前镜像到底启用哪些配置文件,这时就有用了,比如我们通过容器的环境参数app.env来获取当前运行的环境,如果是prod,则激活application-prod.yml;如果是test,则激活application-test.yml。
public class EnvActiveApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
String env = System.getenv("app.env");
if ("prod".equalsIgnoreCase(env)) {
configurableApplicationContext.getEnvironment().setActiveProfiles("prod");
} else if ("test".equalsIgnoreCase(env)) {
configurableApplicationContext.getEnvironment().setActiveProfiles("test");
} else {
throw new RuntimeException("非法的环境参数:" + env);
}
}
}