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;@SpringBootApplicationpublic 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());// 打印所有 beanNameString[] 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