【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

相关文章

Spring Boot中的路径变量示例详解

《SpringBoot中的路径变量示例详解》SpringBoot中PathVariable通过@PathVariable注解实现URL参数与方法参数绑定,支持多参数接收、类型转换、可选参数、默认值及... 目录一. 基本用法与参数映射1.路径定义2.参数绑定&nhttp://www.chinasem.cnbs

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

Java中Integer128陷阱

《Java中Integer128陷阱》本文主要介绍了Java中Integer与int的区别及装箱拆箱机制,重点指出-128至127范围内的Integer值会复用缓存对象,导致==比较结果为true,下... 目录一、Integer和int的联系1.1 Integer和int的区别1.2 Integer和in

SpringSecurity整合redission序列化问题小结(最新整理)

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序... 目录1. 前言2. Redission配置2.1 RedissonProperties2.2 Red

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA