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);}}

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

业务逻辑代码:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/10935.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity学习24:场景scene相关生成,加载,卸载,加载进度,异步加载场景等

目录 1 场景数量 SceneManager.sceneCount 2 直接代码生成新场景 SceneManager.CreateScene 3 场景的加载 3.1 用代码加载场景&#xff0c;仍然build setting里先加入配置 3.2 卸载场景 SceneManager.UnloadSceneAsync(); 3.3 同步加载场景 SceneManager.LoadScene 3.3.…

【Android】布局文件layout.xml文件使用控件属性android:layout_weight使布局较为美观,以RadioButton为例

目录 说明举例 说明 简单来说&#xff0c;android:layout_weight为当前控件按比例分配剩余空间。且单个控件该属性的具体数值不重要&#xff0c;而是多个控件的属性值之比发挥作用&#xff0c;例如有2个控件&#xff0c;各自的android:layout_weight的值设为0.5和0.5&#xff0…

hot100_21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[…

4 [危机13小时追踪一场GitHub投毒事件]

事件概要 自北京时间 2024.12.4 晚间6点起&#xff0c; GitHub 上不断出现“幽灵仓库”&#xff0c;仓库中没有任何代码&#xff0c;只有诱导性的病毒文件。当天&#xff0c;他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒&#xff0c;等待不…

Spring Boot项目中解决跨域问题(四种方式)

目录 一&#xff0c;跨域产生的原因二&#xff0c;什么情况下算跨域三&#xff0c;实际演示四&#xff0c;解决跨域的方法 1&#xff0c;CrossOrigin注解2&#xff0c;添加全局过滤器3&#xff0c;实现WebMvcConfigurer4&#xff0c;Nginx解决跨域5&#xff0c;注意 开发项目…

浅析DNS污染及防范

DNS污染&#xff08;DNS Cache Poisoning&#xff09;是一种网络攻击手段&#xff0c;通过篡改DNS服务器的缓存数据&#xff0c;将域名解析结果指向错误的IP地址&#xff0c;从而误导用户访问恶意网站或无法访问目标网站。这种攻击利用了DNS协议的特性&#xff0c;例如“只认第…

五. Redis 配置内容(详细配置说明)

五. Redis 配置内容(详细配置说明) 文章目录 五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置…

单细胞分析基础-第一节 数据质控、降维聚类

scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…

Qt常用控件 输入类控件

文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1&#xff0c;录入用户信息1.4 例子2&#xff0c;正则验证手机号1.5 例子3&#xff0c;验证输入的密码1.6 例子4&#xff0c;显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1&#xff0c;获取输入框的内容2.4 例…

大模型培训讲师老师叶梓分享:DeepSeek多模态大模型janus初探

以下视频内容为叶梓分享DeepSeek多模态大模型janus的部署&#xff0c;并验证其实际效果&#xff0c;包括图生文和文生图两部分。 叶梓老师人工智能培训分享DeepSeek多模态大模型janus初探 DeepSeek 的多模态大模型 Janus 是一款强大的 AI 模型&#xff0c;专注于图像和文本的多…

Linux系统上安装与配置 MySQL( CentOS 7 )

目录 1. 下载并安装 MySQL 官方 Yum Repository 2. 启动 MySQL 并查看运行状态 3. 找到 root 用户的初始密码 4. 修改 root 用户密码 5. 设置允许远程登录 6. 在云服务器配置 MySQL 端口 7. 关闭防火墙 8. 解决密码错误的问题 前言 在 Linux 服务器上安装并配置 MySQL …

17.2 图形绘制7

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.2.9 字体 17.2.9.1 Font类 Font类定义特定的文本格式&#xff0c;包括字体、字号和样式特性。 Font常用属性&#xff1a; Na…

浅析DDOS攻击及防御策略

DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一种通过大量计算机或网络僵尸主机对目标服务器发起大量无效或高流量请求&#xff0c;耗尽其资源&#xff0c;从而导致服务中断的网络攻击方式。这种攻击方式利用了分布式系统的特性&#xff0c;使攻击规模更大、影响范围更广…

90,【6】攻防世界 WEB Web_php_unserialize

进入靶场 进入靶场 <?php // 定义一个名为 Demo 的类 class Demo { // 定义一个私有属性 $file&#xff0c;默认值为 index.phpprivate $file index.php;// 构造函数&#xff0c;当创建类的实例时会自动调用// 接收一个参数 $file&#xff0c;用于初始化对象的 $file 属…

HarmonyOS NEXT:保存应用数据

用户首选项使用 用户首选项的特点 数据体积小、访问频率高、有加载速度要求的数据如用户偏好设置、用户字体大小、应用的配置参数。 用户搜选项&#xff08;Preferences&#xff09;提供了轻量级配置数据的持久化能力&#xff0c;支持订阅数据变化的通知能力。不支持分布式同…

C++编程语言:抽象机制:模板(Bjarne Stroustrup)

目录 23.1 引言和概观(Introduction and Overview) 23.2 一个简单的字符串模板(A Simple String Template) 23.2.1 模板的定义(Defining a Template) 23.2.2 模板实例化(Template Instantiation) 23.3 类型检查(Type Checking) 23.3.1 类型等价(Type Equivalence) …

OVS-DPDK

dpdk介绍及应用 DPDK介绍 DPDK&#xff08;Data Plane Development Kit&#xff09;是一组快速处理数据包的开发平台及接口。有intel主导开发&#xff0c;主要基于Linux系统&#xff0c;用于快速数据包处理的函 数库与驱动集合&#xff0c;可以极大提高数据处理性能和吞吐量&…

基础项目实战——学生管理系统(c++)

目录 前言一、功能菜单界面二、类与结构体的实现三、录入学生信息四、删除学生信息五、更改学生信息六、查找学生信息七、统计学生人数八、保存学生信息九、读取学生信息十、打印所有学生信息十一、退出系统十二、文件拆分结语 前言 这一期我们来一起学习我们在大学做过的课程…

基于微信小程序的医院预约挂号系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

[Linux]从零开始的STM32MP157 U-Boot移植

一、前言 在上一次教程中&#xff0c;我们了解了STM32MP157的启动流程与安全启动机制。我们还将FSBL的相关代码移植成功了。大家还记得FSBL的下一个步骤是什么吗&#xff1f;没错&#xff0c;就是SSBL&#xff0c;而且常见的我们将SSBL作为存放U-Boot的地方。所以本次教程&…