【Spring Boot 源码学习】SpringApplication 的 run 方法监听器

本文主要是介绍【Spring Boot 源码学习】SpringApplication 的 run 方法监听器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Spring Boot 源码学习系列》

在这里插入图片描述

SpringApplication 的 run 方法监听器

  • 一、引言
  • 二、主要内容
    • 2.1 SpringApplicationRunListeners
    • 2.2 SpringApplicationRunListener
    • 2.3 实现类 EventPublishingRunListener
      • 2.3.1 成员变量和构造方法
      • 2.3.2 成员方法
        • 2.3.2.1 不同阶段的事件处理
        • 2.3.2.2 可用性状态变化事件
    • 2.4 自定义 SpringApplicationRunListener
  • 三、总结

一、引言

书接前文《SpringApplication 的 run 方法核心流程介绍》,Huazie 围绕 SpringApplicationrun 方法,带大家一起初步了解了 Spring Boot 的核心运行流程。其中有关运行流程监听器的内容出现最多,但还未细讲。那么本篇就深入了解下 SpringApplicationrun 方法监听器。

在这里插入图片描述

二、主要内容

注意: 以下涉及 Spring Boot 源码 均来自版本 2.7.9,其他版本有所出入,可自行查看源码。

2.1 SpringApplicationRunListeners

SpringApplicationrun(String... args) 方法中,我们可以看到如下代码:

SpringApplicationRunListeners listeners = getRunListeners(args);

这里是获取一个 SpringApplicationRunListeners 对象,它管理了一组 SpringApplicationRunListener 的监听器集合。

继续查看 getRunListeners 方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}

可以看到 getRunListeners 方法里,直接 new 了一个 SpringApplicationRunListeners 对象并返回,它的构造函数有三个参数:

在这里插入图片描述

  • Log log :日志对象
  • List<SpringApplicationRunListener> listeners :监听器集合
  • ApplicationStartup applicationStartup :应用启动指标对象,通过步骤来记录应用程序启动阶段的情况。核心容器及其基础设施组件可以使用 ApplicationStartup 来标记应用程序启动期间的步骤,并收集有关执行上下文或它们处理时间的数据。

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args) 方法是获取 SpringApplicationRunListener 的监听器集合,如果看过笔者前面的系列文章的朋友,应该对该方法并不陌生。

我们进入 getSpringFactoriesInstances 方法,查看如下:

在这里插入图片描述

可以看到了如下的代码 :

SpringFactoriesLoader.loadFactoryNames(type, classLoader);

这里是通过 SpringFactoriesLoader 类的 loadFactoryNames 方法来获取 META-INF/spring.factories 中配置 key 为 org.springframework.boot.SpringApplicationRunListener 的数据;

在这里插入图片描述

有关实现类 EventPublishingRunListener 请查看 2.3 小节。

继续查看 SpringApplicationRunListeners 方法,可以看到:

在这里插入图片描述

上述标红的方法对应了 Spring Boot 运行流程的不同阶段,这些在《SpringApplication 的 run 方法核心流程介绍》都有介绍过。

starting 方法为例:

在这里插入图片描述
在这里插入图片描述

(listener) -> listener.starting(bootstrapContext)Java 8 中引入的 Lambda 表达式写法【一种简洁的表示匿名函数(没有名称的函数)的方式】。它表示一个接受 SpringApplicationRunListener 类型参数 listener 并且不返回任何结果的函数,函数体内部调用了 listenerstarting 方法。

ConsumerJava 8 中的一个函数式接口,它表示接受一个输入参数并且不返回结果的操作。也就是说,上述的 Lambda 表达式在这里被用来创建 Consumer 接口的一个实例。

在这里插入图片描述

由于 Consumer 接口只有一个抽象方法 accept,上述的 Lambda 表达式将自动实现了这个方法。在 Lambda 表达式中,(listener) 对应 accept 方法的参数,而 -> listener.starting(bootstrapContext) 则定义了当 accept 方法被调用时应该执行的操作。

this.listeners.forEach(listenerAction);

这里遍历了监听器集合中的每个监听器,并执行上述 Lambda 表达式定义的函数。

在这里插入图片描述

2.2 SpringApplicationRunListener

SpringApplicationRunListener 提供了一系列运行流程中回调的方法,如下图所示:

在这里插入图片描述

下面来逐一介绍下【其中一些标注了 @Deprecated 的方法,即表示当前版本废弃的方法】:

  • starting:当 run 方法第一次被执行时,会被立即调用,可用于非常早期的初始化工作。
  • environmentPrepared:当 environment 准备完成,在 ApplicationContext 创建之前,该方法被调用。
  • contextPrepared:当 ApplicationContext 构建完成,资源还未被加载时,该方法被调用。
  • contextLoaded:当 ApplicationContext 加载完成,未被刷新之前,该方法被调用。
  • started:当 ApplicationContext 刷新并启动之后, CommandLineRunnerApplicationRunner 未被调用之前,该方法被调用。
  • ready:当应用程序上下文已刷新,并且所有的 CommandLineRunnerApplicationRunner 都已被调用后,run 方法完成之前,该方法被立即调用。
  • running:同 ready 方法,在当前版本已被废弃。
  • failed:当应用程序出现错误时,该方法被调用。

2.3 实现类 EventPublishingRunListener

EventPublishingRunListenerSpring BootSpringApplicationRunListener 接口的唯一内部实现。

2.3.1 成员变量和构造方法

先来看看它的成员变量和构造方法:

在这里插入图片描述

EventPublishingRunListener 有三个成员变量:

  • SpringApplication application:当前运行的 SpringApplication 实例
  • String[] args:启动应用程序时的命令参数
  • SimpleApplicationEventMulticaster initialMulticaster:事件广播器的简单实现,它会将所有事件多播给所有已注册的监听器,由监听器自身决定是否忽略它们不感兴趣的事件。

EventPublishingRunListener 的构造方法中初始化上述三个变量之后,就会遍历 SpringApplication 中的所有 ApplicationListener 实例,并将它们和 SimpleApplicationEventMulticaster 进行关联,以便后续将事件多播给所有已注册的监听器。

2.3.2 成员方法

在这里插入图片描述
在这里插入图片描述

2.3.2.1 不同阶段的事件处理

通过阅读上述源码,可以大致总结一下 Spring Boot 启动运行的不同阶段的事件处理流程:

  • 首先,Spring Boot 应用程序启动的某个阶段,调用 EventPublishingRunListener 的某个方法;
  • 然后,在这些方法中,将 application 参数和 args 参数【某些事件还有其他参数】封装到对应的事件中,这些事件都是 SpringApplicationEvent 的实现类;
  • 接着,通过成员变量 initialMulticastermulticastEvent 方法对指定事件进行广播 或者 通过当前方法的应用上下文参数 contextpublishEvent 方法来对事件进行发布;
  • 最后,对应的事件监听器 ApplicationListener 被触发,执行相应的业务逻辑。

细心的朋友,可能发现了,上述 EventPublishingRunListener 的某些方法是通过成员变量 initialMulticastermulticastEvent 方法对指定事件进行广播,而某些方法是通过当前方法的应用上下文参数 contextpublishEvent 方法来对事件进行发布。

那这是为啥呢?

在解答这个问题之前,我们先来看下 EventPublishingRunListenercontextLoaded 方法,如下所示:

在这里插入图片描述

大致总结一下 contextLoaded 方法的处理逻辑:

  • 遍历 application 的所有监听器实现类,如果该实现类还实现了 ApplicationContextAware 接口,则将上下文信息设置到该监听器内;
  • application 中的监听器实现类都添加到应用上下文中;
  • 通过成员变量 initialMulticastermulticastEvent 方法对 ApplicationPreparedEvent 事件进行广播。

仔细查看上述源码,我们发现在 contextLoaded 方法之前,都是通过 initialMulticastermulticastEvent 方法进行事件广播的,而在 contextLoaded 方法之后均采用当前方法的应用上下文参数 contextpublishEvent 方法来对事件进行发布的。

现在可以回答上面的疑问了。因为只有调用 contextLoaded 方法之后,应用上下文才算初始化完成,这时才可以通过它的 publishEvent 方法来进行事件的发布。

2.3.2.2 可用性状态变化事件

startedready 方法的实现中,我们还看到 AvailabilityChangeEvent 的调用。

AvailabilityChangeEvent ,即应用程序可用性状态变化事件。任何应用程序组件都可以发送这些事件以更新应用程序的状态。

// started
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);

started 方法发布了一个 LivenessState.CORRECT 类型的可用性状态变化事件。LivenessState 是一个表示可用性状态的枚举类型,其中 CORRECT 表示应用程序正在运行,其内部状态是正确的。它还有一个 BROKEN 的枚举类型,表示应用程序正在运行,但其内部状态已损坏。

// ready
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);

ready 方法发布了一个 ReadinessState.ACCEPTING_TRAFFIC 类型的可用性状态变化事件。ReadinessState 也是一个可用性状态的枚举类型,其中 ACCEPTING_TRAFFIC 表示应用程序已准备好接收流量。它还有一个 REFUSING_TRAFFIC 的枚举类型,表示应用程序不愿意接收流量。

2.4 自定义 SpringApplicationRunListener

了解了这么多关于 SpringApplicationrun 方法监听器的内容,现在让我们来自定义 SpringApplicationRunListener 的接口实现看看,如下所示:

import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;import java.time.Duration;public class DemoRunListener implements SpringApplicationRunListener {public DemoRunListener(SpringApplication application, String[] args) {System.out.println("DemoRunListener的构造方法被调用");}@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {System.out.println("DemoRunListener的 starting 方法被调用");}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {System.out.println("DemoRunListener的 environmentPrepared 方法被调用");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("DemoRunListener的 contextPrepared 方法被调用");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("DemoRunListener的 contextLoaded 方法被调用");}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("DemoRunListener的 started 方法被调用");}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("DemoRunListener的 ready 方法被调用");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("DemoRunListener的 failed 方法被调用");}
}

上述 DemoRunListener 类定义好了之后,我们就可以在 resources 目录下的 META-INF 目录下的 spring.factories 文件中添加如下配置【如果对应的目录和文件没有,则需要自行创建】:

在这里插入图片描述

到这一步,我们就可以启动自己的 Spring Boot 项目,运行结果如下截图:

在这里插入图片描述

从上图中,可以看到不同的启动运行阶段,分别打印了不同的日志出来,说明我们自定义的实现类生效了。

三、总结

本篇博文 Huazie 同大家一起深入分析了 SpringApplicationrun 方法监听器,从配置的加载,接口定义,实现类等方面作了详细了解,最后通过自定义 SpringApplicationRunListener 接口实现并运行查看,进一步加深了理解。

这篇关于【Spring Boot 源码学习】SpringApplication 的 run 方法监听器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/946338

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程