【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

相关文章

springboot健康检查监控全过程

《springboot健康检查监控全过程》文章介绍了SpringBoot如何使用Actuator和Micrometer进行健康检查和监控,通过配置和自定义健康指示器,开发者可以实时监控应用组件的状态,... 目录1. 引言重要性2. 配置Spring Boot ActuatorSpring Boot Act

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python