用Spring的ApplicationEventPublisher进行事件发布和监听

概述

有时候,我们只是想发布一些本地的事件,并不需要引入MQ的,可以直接使用Spring的ApplicationEventPublisher来完成简单事件的发布和监听的。

比如像下面的场景,ApplicationEventPublisher就够用了。

  • 模块间的逻辑解耦,但不跨服务;
  • 轻量级的本地事件通知
  • 应用内部的解耦和扩展点:比如说在DDD里,聚合保存数据成功后,需要触发若干的后续动作
  • 无强事务要求、无强持久化要求的通知场景

生产环境实战,门店创建成功后发布【门店已成功创建的事件】

在我目前这边,门店一旦创建成功后,是有很多一系列的后续动作要去做的,比如配置门店的各种各样的规则信息。

具体的代码很简单。

  • 就是用ApplicationEventPublisher的publishEvent方法发布事件
  • 用EventListener注解监听事件就可以了。

具体的代码实现如下。

事件发布者封装 ApplicationEventPublisher

ShopCreationEventPublisher对 Spring 的ApplicationEventPublisher做了一层封装,统一事件创建和日志输出:

@Component @RequiredArgsConstructor @Slf4j public class ShopCreationEventPublisher { private final ApplicationEventPublisher applicationEventPublisher; public void publishShopCreationEvent(Long shopId) { if (shopId == null) { return; } applicationEventPublisher.publishEvent( new ShopCreationEvent( shopId, ShopConstant.ShopDomainEvent.SHOP_CREATION_EVENT.getCode() ) ); log.info("Publishing {} for shopId: {}", ShopConstant.ShopDomainEvent.SHOP_CREATION_EVENT.getCode(), shopId); } }
@AllArgsConstructor @Getter public enum ShopDomainEvent { SHOP_CREATION_EVENT("shop_creation_event", "门店已创建"), SHOP_UPDATE_EVENT("shop_update_event", "门店已更新"); /** * 编码 */ private final String code; /** * 描述 */ private final String desc; }

这个类做了几件小事:

  • 封装事件构造:上层只需要传shopId,不关心事件 code 的细节
  • ShopDomainEvent:定义门店的时间,有创建和更新

通过这层封装,业务代码几乎完全与ApplicationEventPublisher解耦,只依赖一个语义明确的发布器。ShopApplicationService类只需要调用发布方法,事件就发布出去了。

@RequiredArgsConstructor @Service @Slf4j public class ShopApplicationService { private final ShopCreationEventPublisher shopCreationEventPublisher; /** * 保存门店聚合信息 */ public void saveShopInfo(SaveShopInfoCommand saveShopInfoCommand) { // 构建聚合根 ShopInfoAggregateRoot root = shopInfoFactory.createShopInfoAggregateRoot(saveShopInfoCommand); // 持久化 shopInfoRepository.persistence(root); // 发布【门店已创建】的事件 shopCreationEventPublisher.publishShopCreationEvent(root.getId()); } }

事件监听者实现

用EventListener注解就可以了。

@Component @Slf4j @RequiredArgsConstructor class NewShopRuleGenerateListener { @EventListener public void onShopCreationEvent(ShopCreationEvent event) { log.info("NewShopRuleGenerateListener received : {}", JSON.toJSONString(event)); Long shopId = event.getShopId(); if (shopId == null) { return; } // 新启一个线程执行,避免阻塞事件发布线程 new Thread(() -> { ShopInfoAggregateRoot shopInfoAggregateRoot = shopInfoRepository.selectByShopId(shopId.intValue()); // 新增的门店配置各种各样的规则 }).start(); }

可以看出:

  • 监听方式非常直观:通过@EventListener标注方法,参数就是要监听的事件类型ShopCreationEvent
  • 业务逻辑完全从ShopApplicationService中拆了出来,转移到一个职责清晰的监听器类里
  • 为避免阻塞事件发布线程,监听器内部主动使用新线程执行耗时操作

这条链路的好处在于:

  • 保存门店的主流程对“谁在监听这个事件”是无感知的;
  • 新增或删除监听器只影响监听方代码,不需要修改ShopApplicationService

实战落地建议

尽量为每类事件提供单独的发布器

  • 比如ShopCreationEventPublisherShopUpdatePublisher,而不是在业务代码里直接拿ApplicationEventPublisher
  • 便于后续在发布器层面统一做日志、限流或埋点

事件命名和字段保持业务语义清晰

  • ShopCreationEvent这种名称比“门店变更事件”更具体
  • 事件体只放必要字段,比如shopId、事件类型 code,避免变成“大而全 DTO`

监听器职责要单一

  • 一个监听器专注做一件事情
  • 如果后续还有“新门店初始化库存”、“新门店推送消息”等,可以新建监听器按职责拆分

异步处理尽早抽象到统一机制

  • 目前上面的代码使用new Thread。这个是不太合理的, 应该是用线程池。

在不引入 MQ 的前提下,基于 ApplicationEventPublisher 的这种本地事件机制,可以在保持代码结构清晰的同时,给系统预留足够的扩展点,对很多中小体量的业务来说,这种方案已经足够实用。

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

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

相关文章

nacos服务安装并启动

nacos服务安装并启动1、介绍2、下载nacos3、下载jdk4、修改配置文件5、修改启动程序文件6、启动nacos服务1、介绍 Nacos是一款集服务发现、配置管理与服务管理于一体的云原生平台,旨在帮助开发者更敏捷地构建和管理微服务架构。 2、下载nacos 下载地址&#xff1…

方法类的倒推过程结束 七

完全同意,而且这是让“方法体系”不发散的关键约束:方法里的所有东西都只能引用你世界树里已经存在的节点类型与载体,不允许发明一套“方法专用数据结构宇宙”。 下面我把“条件是什么、动作是什么、结果是什么”用你现有的对象(存在/场景/特征类型/特征值节点等)重新落一…

PaperNex领衔9款AI论文工具实操指南:半天3万字+真实参考文献

前言:为什么你需要这篇AI论文工具实操指南 面对毕业论文、课题申报、期刊投稿,大学生、研究生、科研人员常陷入时间紧、资料杂、写作难的三重困境。AI写作工具的出现,正在把“写作门槛”降到地板级——但要挑对工具、用对方法,才…

人工智能之数字生命-场景类的功能

场景是世界树的“空间承载体”。存在和特征像贴在场景上的标签,而场景本身负责把空间织成一张可走、可查、可拼接的地图。 我按你这段描述,把“场景类应该关心的三类关系”梳理成一个很稳定的结构:内关系、横关系、上关系。对应世界树里也自然形成“场景占多数层”的形态。…

50个域渗透手法全覆盖 万字长文 适合收藏!从零基础入门到精通,收藏这一篇就够了!

50个域渗透手法全覆盖 万字长文 适合收藏! 在大型企业网络攻防演练与真实攻防对抗中,攻击者一旦突破边界进入内网,Active Directory (AD) 域环境便成为核心目标。掌握域渗透的完整路径和多样化手法,既是攻击方扩大战果的关键&…

收藏必备!情境工程:大模型时代企业知识管理系统的革命性变革

文章探讨了情境工程如何重塑企业知识管理系统,从传统"文档存储检索"模式转变为"主动赋能"。通过场景感知、动态连接和人机协同进化,构建企业"智能认知中枢",实现决策质量跃升、组织能力沉淀和创新加速。系统五…

一文搞懂大模型智能体工作原理:从PEAS模型到TAO循环,小白也能轻松入门!

本文拆解了大模型智能体(Agent)的工作原理,通过PEAS模型介绍智能体的四要素(性能指标、环境、执行器、传感器),分析其面对的不完整、不稳定等环境特点,详细解释了"感知→思考→行动→再次感知"的循环过程,以及Thought-A…

智慧校园系统-打造数字化、智能化的教育管理平台

✅作者简介:合肥自友科技 📌核心产品:智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

django-flask基于python的车辆挡泥板机器人工厂管理系统

目录基于Python的车辆挡泥板机器人工厂管理系统摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!基于Python的车辆挡泥板机器人工厂管理系统摘要 该系统采用Django和Flask框架开…

程序员转型大模型产品经理必看:收藏这份详细学习路线_大模型产品经理学习路线详述

本文为有志成为大模型产品的人提供了完整的学习路线,从基础知识到深化理解再到实战演练,系统性地规划了成长路径。文章详细介绍了所需的技术知识、产品管理理论和实战经验,并提供了丰富的学习资源,帮助读者系统掌握大模型产品经理…

django-flask基于python的超市库存管理系统的设计与实现

目录摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!摘要 随着零售行业的快速发展,超市库存管理系统的需求日益增长。传统的库存管理方式依赖人工记录和纸质单据&am…

ARP欺骗攻击的7种解决方案,你知道几种?

ARP欺骗攻击的7种解决方案,你知道几种? 一、ARP 表项固化 如图 1 所示, Attacker 仿冒 UserA 向 Gateway 发送伪造的 ARP报文,导致Gateway的ARP表中记录了错误的UserA地址映射关系,造成 UserA 接收不到正常的数据报文…

深度测评10个AI论文网站,本科生轻松搞定毕业论文!

深度测评10个AI论文网站,本科生轻松搞定毕业论文! AI 工具助力学术写作,让论文不再难 对于许多本科生来说,撰写毕业论文是大学生活中最具挑战性的任务之一。从选题到资料收集,再到大纲搭建和初稿撰写,每一…

django-flask基于python的车牌识别停车场与车辆管理系统

目录django-flask基于python的车牌识别停车场与车辆管理系统的摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式!django-flask基于python的车牌识别停车场与车辆管理系统的摘要 该…

EtherNet/IP转Profibus DP协议转换网关实现汇川PLC与西门子PLC通讯在矿山与冶金的应用案例

项目背景内蒙古包头某矿业集团 2024 年启动“智慧矿山+绿色冶金”升级改造,新建一条 320 t/h 的半自磨矿石输送线、一台 80 MVA 熔炼炉及一条 1 250 mm 热连轧生产线。原有西门子 S7-300 系统(CPU 315-2DP)承担皮带运输、称重计量…

LLM语音合成让医患沟通更顺畅

📝 博客主页:Jax的CSDN主页 LLM语音合成:破解医患沟通壁垒,构建无障碍医疗体验 目录 LLM语音合成:破解医患沟通壁垒,构建无障碍医疗体验 引言:沟通鸿沟下的医疗痛点 维度一:技术应用…

AI知识库(2)豆包AI手机介绍

“豆包AI手机”并不是由字节跳动独立生产的一款品牌手机,而是由字节跳动豆包团队与主流手机厂商(目前主要是中兴旗下的努比亚)深度合作打造的“AI智能体手机”。在2025年底到2026年初这段时间,这款手机因其颠覆性的交互方式在科技…

eSIM工业网关是什么?有什么优势?

eSIM工业网关是集成eSIM技术的工业级通信设备,作为工业物联网的核心枢纽,通过嵌入式eSIM实现设备与网络的稳定连接,并具备协议转换、数据采集、边缘计算、远程管理等功能,为工业物联网各类场景提供丰富可靠的应用。一、eSIM工业网…

InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default

Jackson日期时间配置说明问题描述在使用Java 8的LocalDateTime、LocalDate、LocalTime等时间类型时,Jackson默认不支持序列化和反序列化,会抛出以下异常:​​​​​​​InvalidDefinitionException: Java 8 date/time type java.time解决方案…

如何使用SpringAI来实现一个RAG应用系统

RAG原理大模型没有本地私有知识,所以用户在向大模型提问的时候,大模型只能在它学习过的知识范围内进行回答,而RAG就是在用户在提问的时候 将本地与问题相关的私有知识连同问题一块发送给大模型,进而大模型从用户提供的私有知识范围…