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

news2025/1/27 8:50:08

目录

  • 前言
  • 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 {

    @Autowired
    private EventPublisherService eventPublisherService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public 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 {

    @EventListener
    public 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 {

    @Autowired
    private 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 {

    @Resource
    private 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 {

    @Resource
    private 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.coloradmin.cn/o/2283863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【记录】日常|从零散记录到博客之星Top300的成长之路

文章目录 shandianchengzi 2024 年度盘点概述写作风格简介2024年的创作内容总结 shandianchengzi 2024 年度盘点 概述 2024年及2025年至今我创作了786即84篇文章&#xff0c;加上这篇就是85篇。 很荣幸这次居然能够入选博客之星Top300&#xff0c;这个排名在我之前的所有年份…

定时器按键tim_key模版

低优先级放在高优先级内势必是程序卡死 把高优先级放到低优先级内&#xff0c;会使程序卡死 可修改 Debuger调试方法 Pwm rcc #include "my_main.h" uint8_t led_sta0x10; char text[30]; void LED_Disp(uint8_t dsLED) {HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPI…

Swing使用MVC模型架构

什么是MVC模式? MVC是一组英文的缩写,其全名是Model-View-Controller,也就是“模型-视图-控制器”这三个部分组成。这三个部分任意一个部分发生变化都会引起另外两个发生变化。三者之间的关系示意图如下所示: MVC分为三个部分,所以在MVC模型中将按照此三部分分成三…

ui-automator定位官网文档下载及使用

一、ui-automator定位官网文档简介及下载 AndroidUiAutomator&#xff1a;移动端特有的定位方式&#xff0c;uiautomator是java实现的&#xff0c;定位类型必须写成java类型 官方地址&#xff1a;https://developer.android.com/training/testing/ui-automator.html#ui-autom…

gitee——报错修改本地密码

有时候当我们向远端push本地的仓库时会有一些报错的行为。 如下&#xff1a; 这是因为我们在gitee修改了密码时&#xff0c;本地还没有更新提交&#xff0c;总是报错 解决修改密码报错 如下&#xff1a; 1.在本地点击搜索栏找到控制面板 步骤如下

小盒科技携手体验家,优化智能教育服务体验,打造在线教育新高度

北京小盒科技有限公司&#xff08;简称“小盒科技”&#xff0c;由“作业盒子”更名而来&#xff09;是一家专注于教育科技的公司&#xff0c;致力于利用人工智能、大数据等先进技术&#xff0c;为中小学教育提供创新的解决方案和产品。 近日&#xff0c;「小盒科技」携手体…

Docker Hub 全面解析及应对策略

在现代 DevOps 和容器化应用开发中&#xff0c;Docker Hub 是一个不可或缺的工具。然而&#xff0c;一些地区或企业对 Docker Hub 的访问受到限制&#xff0c;甚至全面禁止。这种现象引发了开发者和运维人员的广泛关注。那么&#xff0c;为什么 Docker Hub 会被禁用&#xff1f…

分布式理解

分布式 如何理解分布式 狭义的分布是指&#xff0c;指多台PC在地理位置上分布在不同的地方。 分布式系统 分布式系**统&#xff1a;**多个能独立运行的计算机&#xff08;称为结点&#xff09;组成。各个结点利用计算机网络进行信息传递&#xff0c;从而实现共同的“目标或者任…

macOS使用LLVM官方发布的tar.xz来安装Clang编译器

之前笔者写过一篇博文ubuntu使用LLVM官方发布的tar.xz来安装Clang编译器介绍了Ubuntu下使用官方发布的tar.xz包来安装Clang编译。官方发布的版本中也有MacOS版本的tar.xz&#xff0c;那MacOS应该也是可以安装的。 笔者2015款MBP笔记本&#xff0c;CPU是intel的&#xff0c;出厂…

ppp综合实验

IP地址 r1 r2 r3 r4 hdlc封装 pap认证 r2 r3 chap认证 r2 r4 MGRE 主认证 [r1]int Tunnel 0/0/0 [r1-Tunnel0/0/0]ip add 192.168.4.1 24 [r1-Tunnel0/0/0]tunnel-protocol gre p2mp [r1-Tunnel0/0/0]source 12.1.1.1 [r1-Tunnel0/0/0]nhrp entry multicast dynamic [r1-Tu…

HBuilderX构建Vue项目

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl HBuilderX概述 HBuilderX是一款专为开发者设计的高效开发工具&#xff0c;致力于提升开发者的编码效率和体验。HBuilderX既适合追求极致效率的极客&#xff0c;也适合希望简…

基于微信小程序的英语学习交流平台设计与实现(LW+源码+讲解)

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

使用 Confluent Cloud 的 Elasticsearch Connector 部署 Elastic Agent

作者&#xff1a;来自 Elastic Nima Rezainia Confluent Cloud 用户现在可以使用更新后的 Elasticsearch Sink Connector 与 Elastic Agent 和 Elastic Integrations 来实现完全托管且高度可扩展的数据提取架构。 Elastic 和 Confluent 是关键的技术合作伙伴&#xff0c;我们很…

JDK17 HashMap

HashMap ArrayList用动态数组存放元素&#xff0c;而HashMap用动态数组&#xff08;桶&#xff09;存储键值对。 如果两个键值对映射到桶同一个索引&#xff0c;则称为散列冲突。HashMap采用拉链法解决冲突&#xff0c;即桶中每个索引指向一个链表或者红黑树&#xff0c;多个键…

ansible自动化运维实战--通过role远程部署nginx并配置(8)

文章目录 1、准备工作2、创建角色结构3、编写任务4、准备配置文件&#xff08;金甲模板&#xff09;5、编写变量6、编写处理程序7、编写剧本8、执行剧本Playbook9、验证-游览器访问每台主机的nginx页面 在 Ansible 中&#xff0c;使用角色&#xff08;Role&#xff09;来远程部…

Python网络自动化运维---用户交互模块

文章目录 目录 文章目录 前言 实验环境准备 一.input函数 代码分段解析 二.getpass模块 前言 在前面的SSH模块章节中&#xff0c;我们都是将提供SSH服务的设备的账户/密码直接写入到python代码中&#xff0c;这样很容易导致账户/密码泄露&#xff0c;而使用Python中的用户交…

计算机的错误计算(二百二十二)

摘要 利用大模型化简计算 实验表明&#xff0c;虽然结果正确&#xff0c;但是&#xff0c;大模型既绕了弯路&#xff0c;又有数值计算错误。 与前面相同&#xff0c;再利用同一个算式看看另外一个大模型的化简与计算能力。 例1. 化简计算摘要中算式。 下面是与一个大模型的…

mybatis(78/134)

前天学了很多&#xff0c;关于java的反射机制&#xff0c;其实跳过了new对象&#xff0c;然后底层生成了字节码&#xff0c;创建了对应的编码。手搓了一遍源码&#xff0c;还是比较复杂的。 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE …

数据分箱 baggingboosting onehot独热编码 woe编码 sklearn的ensemble(集成学习)

目录 数据分箱就是将连续变量离散化。 bagging&boosting onehot独热编码 独热编码的结果如下&#xff1a; woe编码 WOE编码的基本原理 步骤一&#xff1a;计算WOE 步骤二&#xff1a;应用WOE WOE编码的优点 示例 数据示例 步骤一&#xff1a;计算每个类别的违约…

企业微信开发010_使用WxJava企业微信开发框架_封装第三方应用企业微信开发003_并且实现多企业授权访问---企业微信开发012

继续来看吧,上一节,已经把config部分,代码都拿过来了: 并且把企业微信第三方应用开发部分,对应的config的配置,mutiltp 代码拿过来了,并且把yml中的配置也给出了. 然后,这里说一下config中的内容,到时候自己看也可以看懂 其实就是封装了,当系统启动,加载企微模块,这个时候,会…