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

概述

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

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

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

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

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

具体的代码很简单。

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

具体的代码实现如下。

事件发布者封装 ApplicationEventPublisher

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

less 复制代码
@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);
    }
}
less 复制代码
@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类只需要调用发布方法,事件就发布出去了。

less 复制代码
 @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注解就可以了。

less 复制代码
@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 的这种本地事件机制,可以在保持代码结构清晰的同时,给系统预留足够的扩展点,对很多中小体量的业务来说,这种方案已经足够实用。

相关推荐
程序员cxuan11 小时前
Agents.md 是什么
人工智能·后端·程序员
摇滚侠11 小时前
Java 零基础全套教程,类的加载过程与类加载器的理解,笔记 189
java·后端·intellij-idea
ServBay11 小时前
为什么我劝你不要在Mac上用Docker 进行本地 AI 开发
后端
蝎子莱莱爱打怪11 小时前
XZLL-IM干货系列 02|Protobuf 协议设计:从 JSON 切到二进制,每条消息省了 60%
后端·面试·架构
程序员黑豆12 小时前
AI全栈开发之Java:第一个Java程序
前端·后端·ai编程
小Q的编程笔记12 小时前
Pump.fun 的核心是什么?用 300 行 Solidity 实现 Bonding Curve 与自动 LP 销毁
前端·后端·智能合约
学以智用12 小时前
.NET Core Swagger 超详细讲解(从入门到企业级)
后端·.net
浮游本尊12 小时前
Java学习第40天 - 数据库基础、表设计与 Spring Boot 数据访问入门
后端
iOS开发上架哦12 小时前
Jenkins 自动上传 IPA 到 App Store 把发布步骤融入 CI/CD
后端·ios
Java内核笔记12 小时前
SpringSecurity源码解析三:FilterChainProxy核心代理:智能路由、防火墙与请求分发
后端