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

《Spring Boot 源码学习系列》

SpringApplication 的 run 方法监听器

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

一、引言

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

二、主要内容

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

2.1 SpringApplicationRunListeners

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

c 复制代码
SpringApplicationRunListeners listeners = getRunListeners(args);

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

继续查看 getRunListeners 方法:

java 复制代码
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 方法,查看如下:

可以看到了如下的代码 :

java 复制代码
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 方法被调用时应该执行的操作。

java 复制代码
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 ,即应用程序可用性状态变化事件。任何应用程序组件都可以发送这些事件以更新应用程序的状态。

java 复制代码
// started
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);

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

java 复制代码
// ready
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);

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

2.4 自定义 SpringApplicationRunListener

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

java 复制代码
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的构造方法被调用");
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("DemoRunListener的 starting 方法被调用");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("DemoRunListener的 environmentPrepared 方法被调用");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("DemoRunListener的 contextPrepared 方法被调用");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("DemoRunListener的 contextLoaded 方法被调用");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("DemoRunListener的 started 方法被调用");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("DemoRunListener的 ready 方法被调用");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("DemoRunListener的 failed 方法被调用");
    }
}

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

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

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

三、总结

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

相关推荐
javachen__37 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿8 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋8 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
武昌库里写JAVA10 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit11 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm
zru_960212 小时前
Spring Boot 单元测试:@SpyBean 使用教程
spring boot·单元测试·log4j
甄超锋12 小时前
Java Maven更换国内源
java·开发语言·spring boot·spring·spring cloud·tomcat·maven
还是鼠鼠13 小时前
tlias智能学习辅助系统--Maven 高级-私服介绍与资源上传下载
java·spring boot·后端·spring·maven
舒一笑17 小时前
Started TttttApplication in 0.257 seconds (没有 Web 依赖导致 JVM 正常退出)
jvm·spring boot·后端