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

Spring Boot 中的事件发布与监听:深入理解 ApplicationEventPublisher(附Demo)

目录

  • 前言
  • 1. 基本知识
  • 2. Demo
  • 3. 实战代码

前言

🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF

基本的Java知识推荐阅读:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)

1. 基本知识

ApplicationEventPublisher 是 Spring 框架中一个功能接口(@FunctionalInterface),用于发布事件

是 Spring 的事件驱动模型的核心部分,开发者可以通过实现这个接口或通过 Spring 提供的现成实现来发布和管理事件

基本知识如下:

  1. 事件驱动模型
    Spring 提供了一个内置的事件模型,通过事件发布者(ApplicationEventPublisher)和事件监听器(@EventListenerApplicationListener
    事件可以是框架提供的(例如:ContextRefreshedEvent),也可以是用户自定义的事件

  2. @FunctionalInterface 注解
    声明此接口是函数式接口,只有一个抽象方法:publishEvent(Object event)
    可以使用 lambda 表达式或方法引用来实现

  3. 事件类型
    支持两种事件对象:
    ApplicationEvent 类型
    非 ApplicationEvent 类型(会被包装成 PayloadApplicationEvent)

  4. 事件传播特点
    异步/同步:事件的传播方式取决于事件监听器的实现,发布者本身不决定事件的执行方式
    高效性建议:事件监听器应尽量快速完成任务,对于耗时操作建议使用异步处理

主要方法解析

  1. publishEvent(ApplicationEvent event)
    接收 ApplicationEvent 类型事件
    实际是将事件转换为 Object 类型后调用 publishEvent(Object event) 方法
  2. publishEvent(Object event)
    接收任何对象类型的事件
    如果事件不是 ApplicationEvent 类型,会封装为 PayloadApplicationEvent

2. Demo

完整的可执行 Spring Boot 示例,展示了如何使用 ApplicationEventPublisher 实现事件发布和监听功能

这是一个基于 Spring Boot 核心功能的示例,无需 Spring Cloud

项目结构如下:

src/main/java/com/example/demo├── DemoApplication.java├── CustomEvent.java├── CustomEventListener.java├── EventPublisherService.java

截图如下:

在这里插入图片描述

主体流程如下:

+-----------------------+
|   DemoApplication     |   <--- 运行时触发事件发布
+-----------------------+|v
+----------------------------+   1. 通过依赖注入调用服务类
| EventPublisherService      |   
+----------------------------+|v
+-----------------------+     2. 使用 ApplicationEventPublisher 发布事件
| ApplicationEventPublisher |   -------> 发布 CustomEvent
+-----------------------+|v
+----------------------+
|     CustomEvent      |   <--- 发布事件包含消息
+----------------------+|v
+-----------------------+
|   CustomEventListener |
+-----------------------+|v
+-----------------------------+
| 打印 "Received custom event" |
+-----------------------------+
  1. DemoApplication (主类)
package com.example.demo;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication implements CommandLineRunner {@Autowiredprivate EventPublisherService eventPublisherService;public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}@Overridepublic void run(String... args) {// 发布自定义事件eventPublisherService.publishEvent("Hello, ApplicationEventPublisher!");}
}
  1. CustomEvent (事件类)
    继承自 ApplicationEvent
    可以封装任何自定义属性,例如 message
package com.example.demo;import org.springframework.context.ApplicationEvent;public class CustomEvent extends ApplicationEvent {private final String message;public CustomEvent(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}
}
  1. CustomEventListener (事件监听器)
    使用 @EventListener 注解监听特定事件
    方法参数即为监听的事件类型
package com.example.demo;import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class CustomEventListener {@EventListenerpublic void handleCustomEvent(CustomEvent event) {System.out.println("Received custom event: " + event.getMessage());}
}
  1. EventPublisherService (事件发布服务)
    用于在应用上下文中发布事件
    Spring 框架会自动分发事件到匹配的监听器
package com.example.demo;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;@Service
public class EventPublisherService {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void publishEvent(String message) {// 构造并发布事件CustomEvent event = new CustomEvent(this, message);eventPublisher.publishEvent(event);System.out.println("Event published: " + message);}
}

运行 DemoApplication 后,控制台输出如下:

Event published: Hello, ApplicationEventPublisher!
Received custom event: Hello, ApplicationEventPublisher!

截图如下:

在这里插入图片描述

扩展功能

  1. 异步事件监听:在监听方法上加 @Async,并在主类中启用异步:
@SpringBootApplication
@EnableAsync
public class DemoApplication { ... }
  1. 多监听器:可以定义多个监听器监听相同事件,Spring 会自动分发到每个监听器

  2. 自定义事件的继承:可以继承 CustomEvent 定义不同类型的事件,以实现事件的多态性

3. 实战代码

比如发送邮件,如果单纯跟接口进行绑定,代码后续扩展优化会非常冗余!

使用 事件机制(applicationContext.publishEvent 和监听器)

有如下好处:

  1. 解耦业务逻辑
    通过事件机制,发送邮件的逻辑和业务逻辑分离:
    业务代码只需要关心触发“发送邮件”这一行为(发布事件)
    实际的邮件发送逻辑由监听器单独处理

这种解耦方式的优势:

  • 更清晰的代码职责:业务代码不会夹杂具体的邮件发送逻辑
  • 方便扩展:如果未来需要增加更多处理逻辑(如记录日志、重试机制等),可以直接扩展监听器,而不用修改业务代码

而且最主要的是:

  1. 支持异步操作
    在监听器上添加了 @Async 注解,可以让事件的处理逻辑异步执行:
@Async
@EventListener
public void onMessage(MailSendMessage message) {log.info("[onMessage][消息内容({})]", message);mailSendService.doSendMail(message);
}

这样,主线程可以迅速完成主要业务逻辑(如创建发送日志)并返回,而不用等待邮件发送完成

对于高并发场景,这种异步机制非常有用

如果直接在业务方法中调用发送函数,就无法方便地实现异步处理,可能会导致:

  • 性能问题:主线程被邮件发送操作阻塞
  • 用户体验问题:如果邮件发送需要较长时间,业务响应时间会变长
  1. 灵活性和可扩展性
    使用事件机制后,邮件发送的逻辑变成了“事件订阅者”:
    可以轻松增加或移除其他监听器,而不会影响现有的业务代码
    例如,除了发送邮件外,还可以添加监听器发送短信、推送通知、记录操作日志等
    可以根据不同的事件类型(不同的事件类)触发不同的逻辑

上述功能比较抽象,以实际代码为例:

如果做一个接口,发送邮件,信息量很大的时候,需要等这个邮件信息,待结果返回,才可以给客户!

  1. 发送邮件的代码和业务代码紧密耦合,修改或扩展会很麻烦
  2. 如果要实现异步发送,还需要自己额外管理线程池或异步任务,增加复杂度
  3. 如果未来需要在发送邮件时附加其他逻辑(如发送通知),业务代码会变得越来越复杂
public Long sendSingleMail(...) {...// 直接发送邮件mailSendService.doSendMail(...);return sendLogId;
}

以下是结合ApplicationEventPublisher,下述代码以ruoyi-vue-pro代码为例子进行讲解

整体目录如下:

在这里插入图片描述

使用 @EventListener 注解监听特定事件

方法参数即为监听的事件类型
方法参数即为监听的事件类型
方法参数即为监听的事件类型

/*** 针对 {@link MailSendMessage} 的消费者*/
@Component
@Slf4j
public class MailSendConsumer {@Resourceprivate MailSendService mailSendService;@EventListener@Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步public void onMessage(MailSendMessage message) {log.info("[onMessage][消息内容({})]", message);mailSendService.doSendMail(message);}}

对应的实体类:

@Data
public class MailSendMessage {/*** 邮件日志编号*/@NotNull(message = "邮件日志编号不能为空")private Long logId;/*** 接收邮件地址*/@NotNull(message = "接收邮件地址不能为空")private String mail;/*** 邮件账号编号*/@NotNull(message = "邮件账号编号不能为空")private Long accountId;/*** 邮件发件人*/private String nickname;/*** 邮件标题*/@NotEmpty(message = "邮件标题不能为空")private String title;/*** 邮件内容*/@NotEmpty(message = "邮件内容不能为空")private String content;//    private File files;}

对应发送消息:

@Slf4j
@Component
public class MailProducer {@Resourceprivate ApplicationContext applicationContext;/*** 发送 {@link MailSendMessage} 消息** @param sendLogId 发送日志编码* @param mail 接收邮件地址* @param accountId 邮件账号编号* @param nickname 邮件发件人* @param title 邮件标题* @param content 邮件内容*/public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,String nickname, String title, String content) {MailSendMessage message = new MailSendMessage().setLogId(sendLogId).setMail(mail).setAccountId(accountId).setNickname(nickname).setTitle(title).setContent(content);applicationContext.publishEvent(message);}}

实际主体代码是直接使用发送消息,不用对接接收消息,接收消息是直接监听就好!

业务逻辑代码:

在这里插入图片描述

相关文章:

Spring Boot 中的事件发布与监听:深入理解 ApplicationEventPublisher(附Demo)

目录 前言1. 基本知识2. Demo3. 实战代码 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 基本的Java知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&am…...

【Spring】Spring启示录

目录 前言 一、示例程序 二、OCP开闭原则 三、依赖倒置原则DIP 四、控制反转IOC 总结 前言 在软件开发的世界里&#xff0c;随着项目的增长和需求的变化&#xff0c;如何保持代码的灵活性、可维护性和扩展性成为了每个开发者必须面对的问题。传统的面向过程或基于类的设计…...

ospf动态路由配置,cost路径调整,ospf认证实验

一、实验拓扑如图&#xff1a; 接口ip配置网络 &#xff1a;10.17.12.* 10.17.13.* &#xff0c;10.17.23.* 回环接口配置分别为 10.0.1.1 &#xff0c;10.0.1.2&#xff0c;10.0.1.3对应三台路由器 ar1配置接口ip interface GigabitEthernet0/0/0 ip address 10.17.12.1…...

在Rust应用中访问.ini格式的配置文件

在Rust应用中访问.ini格式的配置文件&#xff0c;你可以使用第三方库&#xff0c;比如 ini 或 config. 下面是一个使用 ini 库的示例&#xff0c;该库允许你读取和解析.ini文件。 使用 ini 库 添加依赖 首先&#xff0c;你需要在你的 Cargo.toml 文件中添加 ini 库的依赖&am…...

批量处理多个模型的预测任务

#!/bin/bash# 检查是否传入必要的参数&#xff0c;若未传入参数则打印用法并退出 if [ "$#" -lt 1 ]; thenecho "用法: $0 <file_path>"echo "示例: $0 /home/aistudio/work/PaddleSeg/city/cityscapes_urls_extracted.txt"exit 1 fi# 读取…...

Java 编程初体验

Java学习资料 Java学习资料 Java学习资料 一、引言 在当今数字化的时代&#xff0c;编程已然成为一项极具价值的技能。而 Java 作为一门广泛应用于企业级开发、移动应用、大数据等众多领域的编程语言&#xff0c;吸引着无数初学者投身其中。当我们初次踏入 Java 编程的世界&…...

element-plus 的table section如何实现单选

如果是单选那么全新的按钮应该隐藏或者不可编辑的状态。但是我没找到改变成不可编辑的方法&#xff0c;只能采取隐藏 <template><!-- 注意要包一层div根元素&#xff0c;否则css样式可能会不生效&#xff0c;原因不详 --><div><el-table ref"proTab…...

【JavaEE进阶】图书管理系统 - 壹

目录 &#x1f332;序言 &#x1f334;前端代码的引入 &#x1f38b;约定前后端交互接口 &#x1f6a9;接口定义 &#x1f343;后端服务器代码实现 &#x1f6a9;登录接口 &#x1f6a9;图书列表接口 &#x1f384;前端代码实现 &#x1f6a9;登录页面 &#x1f6a9;…...

牛客周赛 Round 77 题解

文章目录 A-时间表B-数独数组D-隐匿社交网络E-1or0 A-时间表 签到题 #include <bits/stdc.h> using namespace std;int main() {int a[6] {20250121,20250123,20250126,20250206,20250208,20250211};int n; cin >> n;cout << a[n - 1];return 0; }B-数独数…...

Mybatis配置文件详解

MyBatis通过XML或注解的方式将Java对象与数据库中的记录进行映射&#xff0c;极大地简化了数据访问层的开发。而在MyBatis的核心组成部分中&#xff0c;配置文件扮演着举足轻重的角色。它不仅定义了MyBatis的运行环境&#xff0c;还配置了数据源、事务管理、映射器等关键元素&a…...

《深度揭秘:TPU张量计算架构如何重塑深度学习运算》

在深度学习领域&#xff0c;计算性能始终是推动技术发展的关键因素。从传统CPU到GPU&#xff0c;再到如今大放异彩的TPU&#xff08;张量处理单元&#xff09;&#xff0c;每一次硬件架构的革新都为深度学习带来了质的飞跃。今天&#xff0c;就让我们深入探讨TPU的张量计算架构…...

Java基础知识总结(二十二)--List接口

List本身是Collection接口的子接口&#xff0c;具备了Collection的所有方法。现在学习List体系特有的共性方法&#xff0c;查阅方法发现List的特有方法都有索引&#xff0c;这是该集合最大的特点。 List&#xff1a;有序(元素存入集合的顺序和取出的顺序一致)&#xff0c;元素都…...

[STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器

一、定时器简介 STM32 中的定时器&#xff08;TIM&#xff0c;Timer&#xff09;是其最重要的外设之一&#xff0c;广泛用于时间管理、事件计数和控制等应用。 1.1 基本功能 定时功能&#xff1a;TIM定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中…...

算法随笔_27:最大宽度坡

上一篇:算法随笔_26: 按奇偶排序数组-CSDN博客 题目描述如下: 给定一个整数数组 nums&#xff0c;坡是元组 (i, j)&#xff0c;其中 i < j 且 nums[i] < nums[j]。这样的坡的宽度为 j - i。 找出 nums 中的坡的最大宽度&#xff0c;如果不存在&#xff0c;返回 0 。 …...

无公网IP 外网访问本地部署 llamafile 大语言模型

llamafile 是一种AI大模型部署&#xff08;或者说运行&#xff09;的方案&#xff0c;它的特点就是可以将模型和运行环境打包成一个独立的可执行文件&#xff0c;这样就简化了部署流程。用户只需要下载并执行该文件&#xff0c;无需安装运行环境或依赖库&#xff0c;这大大提高…...

使用PC版本剪映制作照片MV

目录 制作MV模板时长调整拖动边缘缩短法分割删除法变速法整体调整法 制作MV 导入音乐 导入歌词 点击歌词 和片头可以修改字体&#xff1a; 还可以给字幕添加动画效果&#xff1a; 导入照片&#xff0c;自动创建照片轨&#xff1a; 修改片头字幕&#xff1a;增加两条字幕轨&…...

搭建 docxify 静态博客教程

首先&#xff0c;安装 node 环境安装 docxify &#xff0c;参考官网&#xff1a;https://docsify.js.org/#/zh-cn/ npm i docsify-cli -g新建docs文件夹专门用来放文章&#xff0c;初始化命令 docsify init ./docs就会生成如下两个文件&#xff0c;index.html 入口文件&#…...

汽车OEMs一般出于什么目的来自定义Autosar CP一些内容

汽车OEMs在使用AUTOSAR CP(Classic Platform)协议时,可能会根据自身的特定需求对标准协议进行修改,形成自己的企业标准(企标)。这种修改通常是为了满足特定的硬件平台、功能需求、安全要求或优化性能。以下是一些常见的修改场景和例子: 1. 硬件平台适配 企业可能会根据…...

Vue.js Vuex 模块化管理

Vue.js Vuex 模块化管理 今天咱们来聊聊如何在 Vuex 中进行模块化管理。当你的 Vue.js 应用变得越来越庞大时&#xff0c;单一的状态管理可能会让人头疼。这时候&#xff0c;Vuex 的模块化功能就派上用场了。 为什么需要模块化&#xff1f; 想象一下&#xff0c;如果把所有的…...

分布式光纤应变监测是一种高精度、分布式的监测技术

一、土木工程领域 桥梁结构健康监测 主跨应变监测&#xff1a;在大跨度桥梁的主跨部分&#xff0c;如悬索桥的主缆、斜拉桥的斜拉索和主梁&#xff0c;分布式光纤应变传感器可以沿着这些关键结构部件进行铺设。通过实时监测应变情况&#xff0c;能够精确捕捉到车辆荷载、风荷…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...