前言
一提到Spring、Springoboot,很多人马上就会想到依赖注入、控制反转、自动装配、约定大于配置、使开发变得简单等等。但是如果仅仅会使用Springboot、SpringMVC完成一些增删改查,解决一些bug,那么实际上你并没有真的懂Spring、Springboot。Spring的核心是容器,Springboot更是封装了Spring,把复杂隐藏在内部,让其在使用上更简单,同时又预留了很多的扩展。所以我认为学会Springboot的简单使用只是一个开始,对业务开发更有参考和学习意义的是Springboot如何把复杂变得简单、预留的扩展接口又是如何使用的。
1. ApplicationContextInitializer的功能作用
- 在Spring容器初始化开始的时候,ApplicationContextInitializer接口的所有实现在类会被实例化;
- 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法会被调用,initialize方法的形参类型是ConfigurableApplicationContext,因此可以认为ApplicationContextInitializer实际上是Spring容器初始化前ConfigurableApplicationContext的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
2. 三种实现方式
下面通过一个具体实现类实现ApplicationContextInitializer接口,来分享一下其具体的实现方式。Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:
1、实现ApplicationContextInitializer接口;
MyApplicationContextInitializer实现ApplicationContextInitializer接口,并打印一下系统相关属性的key、value
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.fanfu.config.MyApplicationContextInitializer
2.2 application.properties
在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册;
context.initializer.classes=com.fanfu.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);
}
}
执行结果如下:(user.dir=项目的根目录)
3. 初始化时机
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();
}
4. 执行时机
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);
}
}
5. 内置的实现类
Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现,常见的实现类如下:
5.1 DelegatingApplicationContextInitializer
ApplicationContextInitializer的第二种实现方式(application.properties),在application.properties文件中配置context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer,DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法
5.2 ContextIdApplicationContextInitializer
ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为“application”
5.3 ConfigurationWarningsApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置,可以看出,该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
5.4 ServerPortInfoApplicationContextInitializer
ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中;
5.5 SharedMetadataReaderFactoryContextInitializer
实例化了一个org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor对象并注册到了Spring的上下文环境中,CachingMetadataReaderFactoryPostProcessor是SharedMetadataReaderFactoryContextInitializer的一个内部类,这里就不具体展开了,有兴趣的小伙伴可以继续跟一下org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor#postProcessBeanDefinitionRegistry方法了解一下具体的内容;
5.6 ConditionEvaluationReportLoggingListener
ConditionEvaluationReportLoggingListener从名字看是一个监听器,实际上不是,其内部类org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.ConditionEvaluationReportListener才是实际的监听器,而ConditionEvaluationReportLoggingListener的作用就是把内部类ConditionEvaluationReportListener注册到Spring上下文环境中;
6. 总结
通过这篇文章,可以了解到:
第一,在Spring容器被刷新前,可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作;
第二,ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的;
第三,了解了ApplicationContextInitializer接口实现类的初始化时机和执行时机,以及Springboot内置的具体实现类,可以学习到spring本身是如何利用扩展接口实现一些功能的,对实际的项目开发具有一定的参考意义。
示例代码地址:凡夫贩夫 / fanfu-web · GitCode(ApplicationContextInitializer分支)