SpringBoot中ApplicationContextInitializer的使用
思考良久,最终还是决定把该初始化器在SpringBoot中的应用也在此处一并说明了(毕竟这块的使用还是比较简单的,所以放一起吧)
熟悉SpringBoot的小伙伴应该知道:它里面大量的使用到了Spring容器上下文启动的相关回调机制:比如SPI、事件/监听、启动器等等。
ApplicationContextInitializer是在springboot启动过程(refresh方法前)调用。提取部分源码参考如下:
public class SpringApplication { // 对象初始化的时候 会从spring.factories里拿出来 private void initialize(Object[] sources) { ... setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); ... } public ConfigurableApplicationContext run(String... args) { ... prepareContext(context, environment, listeners, applicationArguments, printedBanner); ... } private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { ... applyInitializers(context); ... } // 执行处 把所有的遍历执行(已经排序)getInitializers里会根据order进行排序 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); } } }
了解了SpringBoot加载、执行ApplicationContextInitializer的过程,就可以很容易总结出在SpringBoot中自定义使用ApplicationContextInitializer的三种方式:
请注意在SpringBoot中自定义和在Spring Framework中自定义的步骤区别~
SpringBoot中自定义ApplicationContextInitializer的三种方式
spring.factories方式(官方就是这么用的,推荐)
不解释
application.properties添加配置方式
对于这种方式是通过DelegatingApplicationContextInitializer这个初始化类中的initialize方法获取到application.properties中的context.initializer.classes实现类的
所以只需要将实现了ApplicationContextInitializer的类添加到application.properties即可。示例如下:
context.initializer.classes=你的实现类全类名
API方式addInitializers()方法添加
形如:
public static void main(String[] args) { //type01 SpringApplication springApplication = new SpringApplication(Application.class); // 添加进去 springApplication.addInitializers(new Demo01ApplicationContextInitializer()); springApplication.run(args); //SpringApplication.run(InitializerDemoApplication.class,args); }
虽然三种方式都可以,但个人比较推荐的方式为通过spring.factories方式配置
SpringBoot内置的一些ApplicationContextInitializer
下面列出了一个使用缺省配置的Springboot web应用默认所使用到的ApplicationContextInitializer实现们:
DelegatingApplicationContextInitializer
使用环境属性context.initializer.classes指定的初始化器(initializers)进行初始化工作,如果没有指定则什么都不做。
通过它使得我们可以把自定义实现类配置在application.properties里成为了可能
ContextIdApplicationContextInitializer
设置Spring应用上下文的ID,会参照环境属性。至于Id设置为啥值会参考环境属性:
spring.application.name
vcap.application.name
spring.config.name
spring.application.index
vcap.application.instance_index
如果这些属性都没有,ID使用application。
ConfigurationWarningsApplicationContextInitializer
对于一般配置错误在日志中作出警告
ServerPortInfoApplicationContextInitializer
将内置servlet容器实际使用的监听端口写入到Environment环境属性中。这样属性local.server.port就可以直接通过@Value注入到测试中,或者通过环境属性Environment获取。
SharedMetadataReaderFactoryContextInitializer
创建一个SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory对象。实现类为:ConcurrentReferenceCachingMetadataReaderFactory
ConditionEvaluationReportLoggingListener
将ConditionEvaluationReport写入日志。
以上都是SpringBoot内置的上文启动器,可见Spring留出的这个钩子,被SpringBoot发扬光大了。
实际上不仅于此,SpringBoot对Spring Framework的事件监听机制也都有大量的应用~
总结
ApplicationContextInitializer是Spring留出来允许我们在上下文刷新之前做自定义操作的钩子,若我们有需求想要深度整合Spring上下文,借助它不乏是一个非常好的实现。
随便浏览一下SpringBoot的源码可知,它对Spring特征特性的使用,均是非常的流畅且深度整合的。所以说SpringBoot易学难精的最大拦路虎:其实是对Spring Framework系统性的把握~
Tips:spring-test包里有个注解org.springframework.test.context.ContextConfiguration它有个属性可以指定ApplicationContextInitializer辅助集成测试时候的自定义对上下文进行预处理~