当前位置: 首页 > news >正文

Spring Boot 事件监听机制工作原理

前言:

我们知道在 Spring 、Spring Boot 的启动源码中都大量的使用了事件监听机制,也就是我们说的的监听器,监听器的实现基于观察者模式,也就是我们所说的发布订阅模式,这种模式可以在一定程度上实现代码的解耦,但如果想要实现系统层面的解耦,那么就要使用消息队列了,本篇从详细分析一下监听器的原理。

Spring Boot 系列文章传送门

Spring Boot 启动流程源码分析(2)

Spring Boot 启动流程源码分析(2)

Spring Boot 自动配置实现原理(源码分析)

Spring Boot 自定义 starter 启动器

事件监听器的核心元素

  • 事件(ApplicationEvent):监听器触发的原因,当事件源发生了某个事件,对应的监听器就会被触发,Spring 中场常见的事件 ContextRefreshEvent、ContextRStartEvent、ContextStoppedEvent、ContextCloseEvent、ReqyestHandlerEvent 等事件。
  • 监听器(ApplicationListener):监听特定事件,并在事件内部定义了事件发生后的响应逻辑,对应观察者模式的观察者。
  • 事件发布器(ApplicationEventMulticaster):负责发布事件,维护事件和事件监听器之间的关系,并在事件发生时通知相关监听器,对应观察者模式中的被观察者。

监听器的工作流程

  1. 事件监听器注册到事件发起器,用于监听事件。
  2. 事件源产生事件,然后像发布器发布事件。
  3. 事件发布器回调事件监听器的回调方法。
  4. 事件监听器的回调方法被调用,执行业务。

事件发布器的初始化时机

事件发布器又叫事件多播器,我们分析了事件监听器的工作流程,其中事件监听器是要往事件发布器中注册的,那意味着事件监听器开始注册之前已经有了事件发布器,那事件发布器是什么时候初始化的呢?事件发布器是在 AbstractApplicationContext#refresh 方法中调用了 AbstractApplicationContext#registerListeners 方法,完成事件发布器的初始化,如下:

//初始化一个事件多播器
protected void initApplicationEventMulticaster() {//获取 beanFactoryConfigurableListableBeanFactory beanFactory = this.getBeanFactory();//beanFactory 中是否有 应用程序事件多播器if (beanFactory.containsLocalBean("applicationEventMulticaster")) {//有直接赋值this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);if (this.logger.isTraceEnabled()) {this.logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}} else {//没有就创建一个 应用程序事件多播器this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);//应用程序事件多播器 注册到 beanFactory 中beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);if (this.logger.isTraceEnabled()) {this.logger.trace("No 'applicationEventMulticaster' bean, using [" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");}}}

事件监听器的注册时机

事件监听器需要注册到事件发布器中,那事件监听器是设么时候注册的?监听器的注册是在 Spring 容器刷新的时候完成的,AbstractApplicationContext#refresh 方法中调用了 AbstractApplicationContext#registerListeners 方法完成注册,如下:

//注册监听器
protected void registerListeners() {//准备遍历监听器Iterator var1 = this.getApplicationListeners().iterator();while(var1.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var1.next();//往 应用程序事件多播器 中添加监听器this.getApplicationEventMulticaster().addApplicationListener(listener);}//获取所有监听器String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);String[] var7 = listenerBeanNames;int var3 = listenerBeanNames.length;for(int var4 = 0; var4 < var3; ++var4) {String listenerBeanName = var7[var4];//往 应用程序事件多播器 中添加监听器this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}//早期要处理的事件Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {//有早期需要处理的事件Iterator var9 = earlyEventsToProcess.iterator();//遍历派发出去while(var9.hasNext()) {ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();//使用 应用程序事件多播器 将事件派发出去  重点关注 multicastEvent 方法this.getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}

事件监听器在完成事件注册的时候,最后会调用 SimpleApplicationEventMulticaster#multicastEvent 方法(Spring Boot 源码中也会调用这段方法)发布事件,如下:

//org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent)
public void multicastEvent(ApplicationEvent event) {//调用 简单应用程序事件多播器 SimpleApplicationEventMulticaster#multicastEventthis.multicastEvent(event, this.resolveDefaultEventType(event));
}//org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {//解析事件类型ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);//获取线程池Executor executor = this.getTaskExecutor();//迭代遍历监听器Iterator var5 = this.getApplicationListeners(event, type).iterator();while(var5.hasNext()) {//监听器ApplicationListener<?> listener = (ApplicationListener)var5.next();if (executor != null) {//线程池异步发送监听事件executor.execute(() -> {this.invokeListener(listener, event);});} else {//同步发送监听事件this.invokeListener(listener, event);}}}

SimpleApplicationEventMulticaster#multicastEvent 方法中可以发布异步事件,如果事件发布器有线程池就可以发布异步事件。

事件发布器发布完成事件后又是怎么完成事件回调的?

前面我们在分析事件发布的时候最后调用了 SimpleApplicationEventMulticaster#multicastEvent 方法,SimpleApplicationEventMulticaster#multicastEvent 方法中又调用了 SimpleApplicationEventMulticaster#invokeListener 方法,如下:

//org.springframework.context.event.SimpleApplicationEventMulticaster#invokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {//获取 ErrorHandlerErrorHandler errorHandler = this.getErrorHandler();//ErrorHandler  为空判断if (errorHandler != null) {try {//不为空 调用 doInvokeListener 如果有异常 调用errorHandler 来处理this.doInvokeListener(listener, event);} catch (Throwable var5) {errorHandler.handleError(var5);}} else {//没有 errorHandler this.doInvokeListener(listener, event);}}//org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {//执行监听器方法listener.onApplicationEvent(event);} catch (ClassCastException var6) {String msg = var6.getMessage();if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {throw var6;}Log logger = LogFactory.getLog(this.getClass());if (logger.isTraceEnabled()) {logger.trace("Non-matching event type for listener: " + listener, var6);}}}@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1);
}

SimpleApplicationEventMulticaster#invokeListener 方法会对是否有 ErrorHandler 进行不同的处理,但最终都会调用 SimpleApplicationEventMulticaster#doInvokeListener 方法,完成监听器方法的调用。

Spring Boot 是如何完成事件监听的?

Spring Boot 在启动时候调用了 SpringApplication#run 方法,SpringApplication#run 方法中有如下两行代码:

//获取 SpringApplicationRunListener 实例数组 默认获取的是 EventPublishRunListener
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//启动监听 重点关注
listeners.starting(bootstrapContext, this.mainApplicationClass);

我们接着分析 SpringApplicationRunListeners#starting 方法。

SpringApplicationRunListeners#starting 方法源码分析

SpringApplicationRunListeners#starting 方法最终调用了 SimpleApplicationEventMulticaster#multicastEvent 方法,这个方法我们刚刚在上面分析了的,至此回到了 Spring 的方法,Spring Boot 和 Spring 事件监听机制原理基本相同。

//org.springframework.boot.SpringApplicationRunListeners#starting
void starting() {//迭代遍历所有监听器Iterator var1 = this.listeners.iterator();while(var1.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();//调用 事件发布运行监听器 EventPublishingRunListener#starting 方法listener.starting();}}//org.springframework.boot.context.event.EventPublishingRunListener#starting
public void starting() {//调用 简单应用程序事件多播器 SimpleApplicationEventMulticaster#multicastEvent 多播事件 也就是启动监听事件this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

Spring Boot 中事件发布器何时完成初始化的?

我们知道 Spring 中事件发布器是在 AbstractApplicationContext#refresh 方法中完成初始化的的,刚刚上面分析 Spring Boot 的事件发布流程中似乎没有时间发布器的初始化操作,那 Spring Boot 事件发布器是何时初始化的?请看如下代码:

//org.springframework.boot.SpringApplicationRunListeners#starting
void starting() {//迭代遍历所有监听器Iterator var1 = this.listeners.iterator();while(var1.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();//调用 事件发布运行监听器 EventPublishingRunListener#starting 方法listener.starting();}}//org.springframework.boot.context.event.EventPublishingRunListener#starting
public void starting() {//调用 简单应用程序事件多播器 SimpleApplicationEventMulticaster#multicastEvent 多播事件 也就是启动监听事件this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

EventPublishingRunListener#starting 方法中直接使用了 SimpleApplicationEventMulticaster#multicastEvent 发布事件,SimpleApplicationEventMulticaster 作为 EventPublishingRunListener 类的一个属性,在创建 EventPublishingRunListener 对象时候已经初始化了

EventPublishingRunListener 类源码分析

EventPublishingRunListener 这里只是看下构造方法,事件发布器 SimpleApplicationEventMulticaster 作为 EventPublishingRunListener 的一个属性,在 EventPublishingRunListener 完成创建的时候已经初始化了,而 EventPublishingRunListener 又是通过 Spring Boot 自动注入完成创建的。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {private final SpringApplication application;private final String[] args;private final SimpleApplicationEventMulticaster initialMulticaster;public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.initialMulticaster = new SimpleApplicationEventMulticaster();Iterator var3 = application.getListeners().iterator();while(var3.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var3.next();this.initialMulticaster.addApplicationListener(listener);}}//省略部分代码。。。。
}

总结:Spring Boot 事件监听机制工作原理其实和 Spring 的事件监听机制原理一样,Spring Boot 只是通过自动配置简化了一些复杂的配置而已,如果我们熟读 Spring 的源码,那对 Spring Boot 的源码解读是有极大帮助的,Spring Boot 很多功能都是基于 Spring 的,最后本篇关于 Spring Boot 事件监听机制工作原理的分享,希望可以帮助到需要的小伙伴。

如有不正确的地方请各位指出纠正。

相关文章:

Spring Boot 事件监听机制工作原理

前言&#xff1a; 我们知道在 Spring 、Spring Boot 的启动源码中都大量的使用了事件监听机制&#xff0c;也就是我们说的的监听器&#xff0c;监听器的实现基于观察者模式&#xff0c;也就是我们所说的发布订阅模式&#xff0c;这种模式可以在一定程度上实现代码的解耦&#…...

【AI大模型】驱动的未来:穿戴设备如何革新血液、皮肤检测与营养健康管理

文章目录 1. 引言2. 现状与挑战3. AI大模型与穿戴设备概述4. 数据采集与预处理4.1 数据集成与增强4.2 数据清洗与异常检测 5. 模型架构与训练5.1 高级模型架构5.2 模型训练与调优 6. 个性化营养建议系统6.1 营养建议生成优化6.2 用户反馈与系统优化 7. 关键血液成分与健康状况评…...

【FFmpeg】avcodec_open2函数

目录 1. avcodec_open21.1 编解码器的预初始化&#xff08;ff_encode_preinit & ff_decode_preinit&#xff09;1.2 编解码器的初始化&#xff08;init&#xff09;1.3 释放编解码器&#xff08;ff_codec_close&#xff09; FFmpeg相关记录&#xff1a; 示例工程&#xff…...

matlab:对带参数a关于x的方程求解

题目 讲解 简洁对各个式子的内部含义用浅显易懂的话语总结出来了&#xff0c;耐心体会 f(a) (x)exp(x)x^ax^(sqrt(x))-100;%因为下面的fzero的第一个数需要一个fun&#xff0c;所以这里有两个句柄&#xff0c;第一个a是输入的&#xff0c;第二个x是需要被解出的 A0:0.1:2;%创…...

Yolov10训练,转化onnx,推理

yolov10对于大目标的效果好&#xff0c;小目标不好 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 目录 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 二、配置好后就可以配置文件…...

GEE代码实例教程详解:洪水灾害监测

简介 在本篇博客中&#xff0c;我们将使用Google Earth Engine (GEE) 进行洪水灾害监测。通过分析Sentinel-1雷达数据&#xff0c;我们可以识别特定时间段内的洪水变化情况。 背景知识 Sentinel-1数据集 Sentinel-1是欧洲空间局提供的雷达卫星数据集&#xff0c;它能够提供…...

运维锅总详解系统设计原则

本文对CAP、BASE、ACID、SOLID 原则、12-Factor 应用方法论等12种系统设计原则进行分析举例&#xff0c;希望对您在进行系统设计、理解系统运行背后遵循的原理有所帮助&#xff01; 一、CAP、BASE、ACID简介 以下是 ACID、CAP 和 BASE 系统设计原则的详细说明及其应用举例&am…...

深度学习笔记: 最详尽解释预测系统的分类指标(精确率、召回率和 F1 值)

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家&#xff01; 预测系统的分类指标(精确率、召回率和 F1 值) 简介 让我们来谈谈预测系统的分类指标以及对精确率、召回…...

GEE代码实例教程详解:MODIS土地覆盖分类与面积计算

简介 在本篇博客中&#xff0c;我们将使用Google Earth Engine (GEE) 对MODIS土地覆盖数据进行分析。通过MODIS/061/MCD12Q1数据集&#xff0c;我们可以识别不同的土地覆盖类型&#xff0c;并计算每种类型的总面积。 背景知识 MODIS MCD12Q1数据集 MODIS/061/MCD12Q1是NASA…...

LT86101UXE 国产原装 HDMI2.0 / DVI中继器方案 分辨率 4Kx2K 用于多显示器 DVI/HDMI电缆扩展模块

1. 描述 Lontium LT86101UXE HDMI2.0 / DVI中继器特性高速中继器符合HDMI2.0/1.4规范,最大6 gbps高速数据率、自适应均衡RX输入和pre-emphasized TX输出支持长电缆应用程序,没有晶体在船上保存BOM成本,内部灵活的PCB TX巷交换路由。 LT86101UXE HDMI2.0/DVI中继器自动检测线缆损…...

FastApi中的常见请求类型

FastApi中的常见请求类型 后端开发语言中&#xff0c;我钟情于node&#xff0c;高效的异步处理真是让我眼前一亮&#xff0c;同时&#xff0c;简单易懂的语法也让我非常倾心 但是但是&#xff0c;因为考虑要写一个深度学习算法的后端接口&#xff0c;所以不得不选用python作为…...

服务器,云、边缘计算概念简单理解

目录 服务器,云、边缘计算概念简单理解 一、服务器 二、云计算 三、边缘计算 服务器和云之间区别 性质 可用性 弹性扩展 管理和维护 成本 应用场景 服务器,云、边缘计算概念简单理解 一、服务器 概念简单理解: 服务器是计算机网络上最重要的设备之一,它在网络…...

【Linux系列2】Cmake安装记录

方法一 1. 查看当前cmake版本 [rootlocalhost ~]# cmake -version cmake version 2.8.12.22. 进行卸载 [rootlocalhost ~]# yum remove -y cmake3. 进行安装包的下载&#xff0c;也可以下载好安装包后传至相应的目录 [rootlocalhost ~]# mkdir /opt/cmake [rootlocalhost ~…...

C++ STL 多线程库用法介绍

目录 一&#xff1a;Atomic&#xff1a; 二&#xff1a;Thread 1. 创建线程 2. 小心移动(std::move)线程 3. 如何创建带参数的线程 4. 线程参数是引用类型时&#xff0c;要小心谨慎。 5. 获取线程ID 6. jthread 7. 如何在线程中使用中断 stop_token 三&#xff1a;如何…...

Jmeter实现接口自动化

自动化测试理论知识 什么是自动化测试&#xff1f; 让程序或工具代替人为执行测试用例什么样的项目适合做自动化&#xff1f; 1、项目周期长 --多长算长&#xff1f;&#xff08;自己公司运营项目&#xff09; 2、需求稳定&#xff08;更多具体功能/模块&#xff09; 3、需要…...

【大模型】多模型在大模型中的调度艺术:解锁效率与协同的新境界

多模型在大模型中的调度艺术&#xff1a;解锁效率与协同的新境界 引言一、多模型与大模型的概念解析二、多模型调度的必要性三、多模型调度的关键技术3.1 负载均衡与动态分配3.2 模型间通信与协作3.3 模型选择与优化 四、多模型运行优化策略4.1 异构计算平台的利用4.2 模型压缩…...

LeetCode 704, 290, 200

目录 704. 二分查找题目链接标签思路代码 290. 单词规律题目链接标签思路代码 200. 岛屿数量题目链接标签思路代码 704. 二分查找 题目链接 704. 二分查找 标签 数组 二分查找 思路 这道题是 二分查找 最经典的一道题&#xff0c;掌握了本题的思想就进入了 二分 思想的大…...

如何利用Java进行大数据处理?

如何利用Java进行大数据处理&#xff1f; 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. 引言 在当今信息爆炸的时代&#xff0c;处理大数据是许多应用程序和系统的核心需求之一。Java作为一种…...

【论文通读】GUICourse: From General Vision Language Model to Versatile GUI Agent

GUICourse: From General Vision Language Model to Versatile GUI Agent 前言AbstractMotivationSolutionGUICourseGUIEnvGUIEnv-globalGUIEnv-local GUIActGUIAct (web-single)GUIAct (web-multi)GUIAct (smartphone) GUIChat ExperimentsMain ResultAblation Study Conclusi…...

王道考研数据机构:中缀表达式转为后缀表达式

实现方法&#xff1a; 初始化一个栈&#xff0c;用于保存暂时还不能确定运算顺序的运算符。从左到右处理各个元素&#xff0c;直到末尾。可能遇到三种情况: 遇到操作数。直接加入后缀表达式遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式&…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...