【Spring Boot 源码学习】ApplicationListener 详解

《Spring Boot 源码学习系列》

引言

书接前文《初识 SpringApplication》,我们从 Spring Boot 的启动类 SpringApplication 上入手,了解了 SpringApplication 实例化过程。其中,《BootstrapRegistryInitializer 详解》 和 《ApplicationContextInitializer 详解》博文中,Huazie 已经带大家详细分析了 BootstrapRegistryInitializer 和 ApplicationContextInitializer 的加载和初始化过程,如下还有 2.5 还未详细分析:

那本篇博文就主要围绕 2.5 的内容展开,详细分析一下 ApplicationListener 的加载和处理应用程序事件的逻辑。

主要内容

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

1. 初识 ApplicationListener

我们先来看看 ApplicationListener 接口的源码【spring-context-5.3.25.jar】:

java 复制代码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    void onApplicationEvent(E event);

    static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
        return event -> consumer.accept(event.getPayload());
    }
}

从上述代码,我们可以看到 ApplicationListener 接口被 @FunctionalInterface 注解修饰。

知识点: @FunctionalInterface 是 Java 8 中引入的一个注解,用于标识一个函数式接口。函数式接口是只有一个抽象方法的接口,常用于实现 Lambda 表达式和方法引用。 使用 @FunctionalInterface 注解可以向编译器指示该接口是一个函数式接口,从而在编译时进行类型检查,确保该接口 只包含一个抽象方法。此外,该注解还可以为函数式接口生成特殊的方法,如默认方法(default method)和 静态方法(static method),这些方法可以在接口中提供更多的功能,这里就不赘述了,感兴趣的朋友可以自行查阅相关函数式接口的资料。

ApplicationListener 是 Spring 中应用程序事件监听器实现的接口。它基于观察者设计模式的java.util.EventListener 接口的标准。在注册到 Spring ApplicationContext 时,事件将进行相应的过滤,只有匹配的事件对象才会使该监听器被调用。

在 ApplicationListener 接口中,我们可以看到它定义了一个 onApplicationEvent(E event) 方法,当监听事件被触发时,onApplicationEvent 方法就会被调用执行。onApplicationEvent 方法一般用于处理应用程序事件,参数 event 为 ApplicationEvent 的子类,也就是具体要响应处理的各种类型的应用程序事件。例如,当某个特定事件发生时,你可能想要记录日志、更新数据库、发送电子邮件等等。

另外,ApplicationListener 接口还提供了一个静态方法 forPayload(Consumer<T> consumer),用于创建一个新的 ApplicationListener 实例。这个方法接受一个 Consumer<T> 类型的参数,这个参数是一个函数接口,它接受一个泛型参数 T,并对其执行一些操作。通过这个方法,你可以将一个 Consumer 函数作为参数,然后返回一个对应的事件监听器。这个监听器会在事件发生时,调用 Consumer 函数处理事件的有效载荷【即事件中包含的有效信息或数据】。

2. 加载 ApplicationListener

java 复制代码
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

上述代码是 SpringApplication 的核心构造方法中的逻辑,它用于加载实现了 ApplicationListener 接口的监听器实例集合,并将该监听器实例集合设置到 SpringApplication 的 listeners 变量中。

java 复制代码
private List<ApplicationContextInitializer<?>> initializers;

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

我们看到了如下的代码 :

java 复制代码
SpringFactoriesLoader.loadFactoryNames(type, classLoader);

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

我们以 spring-boot-autoconfigure-2.7.9.jar 为例:

bash 复制代码
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

3. 响应应用程序事件

这里我们需要查看 SpringApplication 的 run(String... args) 方法,如下所示:

我们看上面的 SpringApplicationRunListeners ,其内的 listeners 变量是 SpringApplicationRunListener 接口的集合,如下所示:

而 SpringApplicationRunListener 接口的一个实现就是 EventPublishingRunListener 类,该类的作用就是根据 Spring Boot 程序启动过程的 不同阶段 发布对应的事件,然后由不同的实现 ApplicationListener 接口的应用程序监听器,来处理对应的事件【有关 SpringApplicationRunListener 监听器的内容,我们后续博文中会详细介绍,这里不展开了】。

如下图是 SpringApplicationRunListeners 类中的方法,它们分别对应了 Spring Boot 程序启动过程中要发布的不同阶段的事件的逻辑。

  • starting :当 run 方法第一次被执行时,该方法会立即被调用,可用于非常早期的初始化工作
  • environmentPrepared :当 environment 准备完成,在 ApplicationContext 创建之前,该方法被调用
  • contextPrepared :当 ApplicationContext 构建完成,资源还未被加载时,该方法被调用
  • contextLoaded :当 ApplicationContext 加载完成,未被刷新之前,该方法被调用
  • started :当 ApplicationContext 刷新并启动之后,CommandLineRunner 和 ApplicationRunner 未被调用之前,该方法被调用
  • ready :当所有准备工作就绪,run 方法执行完成之前,该方法被调用
  • failed :当应用程序出现错误时,该方法被调用

我们以 starting 方法的逻辑为例,看一下 ApplicationStartingEvent 事件发布并被处理的过程。

java 复制代码
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
        (step) -> {
            if (mainApplicationClass != null) {
              step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
        });
}

我们继续看 doWithListeners 方法:

结合上面的截图,我们重点看下这行:

java 复制代码
(listener) -> listener.starting(bootstrapContext)

这里时调用了 SpringApplicationRunListener 接口的 starting 方法:

这里的 multicastEvent 方法就是用来发布一个指定的应用程序事件,比如这里发布的就是 ApplicationStartingEvent 事件。

总结

本篇 Huazie 带大家详细分析了 Spring Boot 中 ApplicationListener 的加载和处理应用程序事件,这对于后续的 SpringApplication 运行流程的理解至关重要。

相关推荐
驭渊的小故事7 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客7 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1887 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn8 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su8 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123459 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu10 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠10 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python
旋律41411 小时前
同义方法区别(增加类与删除类)
java·后端