SpringBoot/Spring扩展点系列之初出茅庐ApplicationContextInitializer - 第426篇
导读
对于Spring Boot为我们提供了很多的扩展点,以供我们进行自身的业务逻辑的处理。对于大部分的扩展点以其说是Spring Boot的,更确切的说是Spring的扩展点,但Spring Boot对于这些扩展点恰到好处的进行了使用。
那么都有哪些扩展点呢?千言万语,不如一图:
SpringBoot/Spring扩展点系列:
(1)✅《SpringBoot/Spring扩展点之初出茅庐ApplicationContextInitializer》
(2)待定
这一节我们就来看看《SpringBoot/Spring扩展点之初出茅庐ApplicationContextInitializer》
一、ApplicationContextInitializer基本概念
1.1 ApplicationContextInitializer是什么?
ApplicationContextInitializer是Spring框架原有的东西,这个类的主要作用就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfiurableApplicationContext的实例做进一步的设置和处理。
ApplicationContextInitializer接口是在spring容器刷新之前执行的一个回调函数。是在ConfigurableApplicationContext#refresh() 之前调用(当spring框架内部执行 ConfigurableApplicationContext#refresh()方法的时候或者在SpringBoot的run()执行时),作用是初始化Spring ConfigurableApplicationContext的回调接口。
简单理解就是:用于在刷新容器之前初始化Spring的回调接口。
1.2 ApplicationContextInitializer作用
ApplicationContextInitializer这个类的主要目的就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理。
通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活概要文件。
1.3 ApplicationContextInitializer使用场景
该接口典型的应用场景是web应用中需要编程方式对应用上下文做初始化。比如,注册属性源(propertysources)或者针对上下文的环境信息environment激活相应的profile。
对于Spring Boot 内置的一些ApplicationContextInitializer,用于实现 Web 配置,日志配置等功能。
二、ApplicationContextInitializer扩展实现方式
下面列出了一个使用缺省配置的SpringBoot web应用默认所使用到的ApplicationContextInitializer实现:
2.1 编程方式
2.1.1定义ApplicationContextInitializer
先定义ApplicationContextInitializer:
package com.kfit.config;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* ApplicationContextInitializer扩展点
*
* @Order的value值越小->越早执行。注:在类上标注,不是方法上
*
* @author 悟纤「公众号SpringBoot」
* @date 2022-05-16
* @slogan 大道至简 悟在天成
*/
public class ApplicationContextInitializer1 implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer1.initialize");
}
}
2.1.2启动类里手动增加initializer
启动类里手动增加initializer:
package com.kfit;
import com.kfit.config.ApplicationContextInitializer1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootApplicationcontextinitializerExtendApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringBootApplicationcontextinitializerExtendApplication.class);
// 方法一:添加自定义的 ApplicationContextInitializer 实现类的实例(注册ApplicationContextInitializer)
springApplication.addInitializers(new ApplicationContextInitializer1());
springApplication.run(args);
}
}
启动就可以再控制台进行信息的打印了。
2.2 application.properties添加配置方式
对于这种方式是通过DelegatingApplicationContextInitializer这个初始化类中的initialize方法获取到application.properties中context.initializer.classes对应的类并执行对应的initialize方法。只需要将实现了ApplicationContextInitializer的类添加到application.properties即可
对于DelegatingApplicationContextInitializer是SpringBoot框架实现的。
2.2.1 定义ApplicationContextInitializer
先定义ApplicationContextInitializer:
package com.kfit.config;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* ApplicationContextInitializer扩展点
*
* @Order的value值越小->越早执行。注:在类上标注,不是方法上
*
* @author 悟纤「公众号SpringBoot」
* @date 2022-05-16
* @slogan 大道至简 悟在天成
*/
public class ApplicationContextInitializer2 implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer2.initialize");
}
}
2.2.1 application.properties添加配置
将实现了ApplicationContextInitializer的类添加到application.properties:
#多个使用逗号隔开
context.initializer.classes=com.kfit.config.ApplicationContextInitializer2
如果有多个要配置的话,使用逗号进行隔开。
到此运行一下就可以看到控制台的信息打印了,此时说明配置信息生效了。
2.3使用spring.factories方式(SPI方式)
2.3.1 定义ApplicationContextInitializer
先定义ApplicationContextInitializer:
package com.kfit.config;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* ApplicationContextInitializer扩展点
*
* @Order的value值越小->越早执行。注:在类上标注,不是方法上
*
* @author 悟纤「公众号SpringBoot」
* @date 2022-05-16
* @slogan 大道至简 悟在天成
*/
public class ApplicationContextInitializer3 implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer3.initialize");
}
}
2.3.2 在spring.factories添加配置
在项目下的resources下新建META-INF文件夹,文件夹下新建spring.factories文件:
org.springframework.context.ApplicationContextInitializer=com.kfit.config.ApplicationContextInitializer3
自此可以运行看小效果。
虽然这三种方式都可以进行实现对于ApplicationContextInitializer的扩展,但建议优先使用方式二和方式三。
三、ApplicationContextInitializer的进阶使用
对于上面的例子对于ApplicationContextInitializer有了一个基本的认知,但对于项目开发还是不够的,自此我们在来看一下进阶的使用。
在网络上找资料,很多文章也是停留在了基本使用层面的介绍。
3.1 执行顺序问题
如果使用了上面的3中方式,那么默认的执行顺序是这样子的:
如果要改变他们的执行顺序,就需要使用到@Order注解,数字越小越先执行。
如果要实现1先执行,那么就是:
@Order(-1)
public class ApplicationContextInitializer1 implements ApplicationContextInitializer{}
那么执行结果就是:
这里@Order的值为0的话,执行结果是这样子的:
这是因为application.properties的方式是通过DelegatingApplicationContextInitializer来实现的,默认的order=0,具体下文会详细说明。
3.2 获取bean的信息
先看下代码:
System.out.println("bean count:"+applicationContext.getBeanDefinitionCount());
// 打印所有 beanName
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
System.out.println(beanName);
}
这里要注意一下,这个目前获取到的是Spring框架自身初始化需要用到的bean信息,我们自身写的bean并未初始化。
3.3 是否可以获取到自定义的bean?
在这里我们一个DemoService,然后尝试去获取一下:
applicationContext.getBean(DemoService.class);
执行直接报错:
3.4 激活配置文件
可以在这个initialize激活配置文件:
//激活测试环境的配置
applicationContext.getEnvironment().setActiveProfiles("test");
当然如果你使用了SpringBoot的话,SpringBoot在这一块就做的很好了,就没有在这里设置了,但这里确实提供了一个可以配置的入口。
3.5 注册bean后置处理器
在这里可以使用applicationContext的add注册后置处理器:
//注册后置处理器
applicationContext.addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
具体的使用方式可以翻看ConfigurationWarningsApplicationContextInitializer的源码,这是Spring Boot框架的一个类,下面会进行说明。
3.6 注册bean单例
具体的代码如下:
//注册bean单例
DemoService demoService = new DemoService();
applicationContext.getBeanFactory().registerSingleton("demoService",demoService);
具体可以类ContextIdApplicationContextInitializer的实现,下面会介绍到。
四、SpringBoot内置的一些ApplicationContextInitializer
Spring 提供了扩展ApplicationContextInitializer 的方法,Spring Boot 将其发扬光大了。我们可以在 spring-boot 的 jar 包下的 META-INF 中找到 spring.factories,如下是其中的 ApplicationContextInitializer 的配置。
4.1 ConfigurationWarningsApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置,可以看出,该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
4.2 ContextIdApplicationContextInitializer
ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,这个ID 可以通过 ApplicationContext#getId() 的方式获得。
4.3 DelegatingApplicationContextInitializer
DelegatingApplicationContextInitializer看到 Delegating 就知道了,这个初始化器是为了别人服务的。这个初始化器会获得application.properties 下的配置为context.initializer.classes 的值,这个值是初始化器的全路径名,多个之间用逗号隔开。获得名称后,使用反射将其实例化,并依次触发初始化器的 initialize 方法。DelegatingApplicationContextInitializer使得 Spring Boot 的用户可以在application.properties 中配置初始化器。需要注意的是,DelegatingApplicationContextInitializer的优先级是 0 ,所以不论context.initializer.classes 配置的初始化器的 order 是多少,都会按照 0 的优先级执行。
4.4 RSocketPortInfoApplicationContextInitializer和ServerPortInfoApplicationContextInitializer
RSocketPortInfoApplicationContextInitializer和 ServerPortInfoApplicationContextInitializer 都是给 ApplicationContext 增加了个监听器,二者都是监听RSocketServerInitializedEvent 事件,为环境 Environment 中添加一个属性源,不同之处在于一个是增加 SocketPort,一个是增加 ServerPort。
4.5 其它说明
除了 spring-boot 下的META-INF/spring.factories 存在初始化器外,spring-boot-autoconfigure下也存在 META-INF/spring.factories。这里也定义了两个初始化器。从这里也可以看出,使用 SPI 的方式确实降低了项目间的耦合,每个项目都能定义自己的实现。
SharedMetadataReaderFactoryContextInitializer:
创建一个SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory对象。实现类为:ConcurrentReferenceCachingMetadataReaderFactory。
ConditionEvaluationReportLoggingListener:
将ConditionEvaluationReport写入日志。
以上都是SpringBoot内置的上文启动器,可见Spring留出的这个钩子,被SpringBoot发扬光大了。
实际上不仅于此,SpringBoot对Spring Framework的事件监听机制也都有大量的应用~
五、ApplicationContextInitializer的执行阶段
Spring Boot 执行 main 方法,其实就是执行 SpringApplication 的 run 方法。
run 方法是 SpringApplication 的静态方法,其中会生成 SpringApplication实例对象,真正执行的是实例对象的 run 方法。SpringFactoriesLoader加载 ApplicationContextInitializer 的过程就发生在生成 SpringApplication 实例的过程中。 类加载完毕,且生成了实例,那这些初始化器什么时候生效呢?如下是 run 方法执行流程。
ApplicationContextInitializer 是在准备Application 的上下文阶段被执行的。我们知道,Spring是在刷新上下文的时候开始通过 BeanFactory 加载 Bean,所以,ApplicationContextInitializer 的执行发生在 Bean 加载之前,但是此时的 Environment已经初始化完毕,我们可以在该阶段获得 Environment 的实例,方便增加或修改一些值;此时ApplicationContext 实例也创建好了,可以预先在上下文中加入一些监听器,处理器等。
applyInitializers方法中,会遍历之前注册的 initializers,依次调用 initialize 方法。
总结
(1)ApplicationContextInitializer 是 Spring 对外提供的扩展点之一,用于在 ApplicationContext 容器加载 Bean 之前对当前的上下文进行配置。
(2)ApplicationContextInitializer 的实现有三种,第一种是在 classpath 路径下的META-INF/spring.factories 文件中填写接口和实现类的全名,多个实现的话用逗号分隔。第二种是在 Spring Boot 启动代码中手动添加初始化器,第三种是在application.properties 中配置 context.initializer.classes。
(3)SpringFactoriesLoader 是 spring 提供的,用于加载外部项目配置的加载器。他会固定的读取 META-INF/spring.factories 文件,解析该文件,获得指定接口的实现类。SpringFactoriesLoader 这种加载配置的方式是典型的 SPI 方式,在 Spring Boot 中大量使用,这种方式将服务接口与服务实现分离,达到解耦、提高可扩展性的目的。
(4)Spring Boot 内置了一些初始化器,大部分功能是配置环境变量,比如 ServerPortInfoApplicationContextInitializer,实现手段是为 ApplicationContext 增加监听器。还用于配置日志,比如ConfigurationWarningsApplicationContextInitializer 实现手段是增加 Bean 后处理器做校验。比较特殊的是DelegatingApplicationContextInitializer,它会获得application.properties 中配置的 context.initializer.classes,将其作为初始化器进行加载和执行。
购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287