Spring 事件机制,比我想的好用

举报
yd_241780069 发表于 2026/11/03 22:46:31 2026/11/03
【摘要】 起因下单成功后要干几件事:发通知、加积分、写操作日志。一开始全塞在 createOrder 方法里,一个方法两百多行,改一处怕碰坏另一处。后来试了试 Spring 的事件机制,把这些拆出去,主流程清爽了不少。不是所有解耦都得靠 MQ。进程内的事情,Spring 事件够用了,还不用多维护一个中间件。 基本用法三个东西:事件、发布者、监听者。事件就是个普通类,继承 ApplicationEve...

起因

下单成功后要干几件事:发通知、加积分、写操作日志。一开始全塞在 createOrder 方法里,一个方法两百多行,改一处怕碰坏另一处。后来试了试 Spring 的事件机制,把这些拆出去,主流程清爽了不少。

不是所有解耦都得靠 MQ。进程内的事情,Spring 事件够用了,还不用多维护一个中间件。

基本用法

三个东西:事件、发布者、监听者。

事件就是个普通类,继承 ApplicationEvent 或者干脆不继承(Spring 4.2 之后支持任意对象):

public class OrderCreatedEvent {
    private final Long orderId;
    private final Long userId;

    public OrderCreatedEvent(Long orderId, Long userId) {
        this.orderId = orderId;
        this.userId = userId;
    }
    // getter
}

发布用 ApplicationEventPublisher:

@Service
public class OrderService {
    private final ApplicationEventPublisher publisher;

    public OrderService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Transactional
    public void createOrder(OrderReq req) {
        Order order = orderDao.insert(req);
        publisher.publishEvent(new OrderCreatedEvent(order.getId(), req.getUserId()));
    }
}

监听:

@Component
public class OrderEventListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // 发通知、加积分、写日志
    }
}

一个事件可以被多个监听器接,各干各的,互不影响。加积分和发通知拆成两个监听器,以后想删哪个改哪个,动一处不碰另一处。

默认是同步的

publishEvent 默认同步执行,监听器跑完才返回。也就是说,createOrder 里发完事件,得等发通知、加积分全跑完,接口才返回。

这个在有些场景下不能接受。发通知要走 HTTP 调第三方,慢的时候好几秒,用户下单等不起。这时候在监听器上加 @Async,配合 @EnableAsync:

@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) { }

变成异步之后,主流程发完事件立刻返回,监听器在另一个线程里跑。代价是异常不好处理,监听器里抛了异常,主流程是感知不到的,得自己在监听器里 try-catch 记日志。

事务边界,这个得注意

最开始的写法有问题。createOrder 标了 @Transactional,事件在事务提交之前就发出去了。监听器里查订单,如果用的是同一个事务,能查到;如果监听器开了新事务或者走了异步,可能查不到——事务还没提交呢。

后来换成了 @TransactionalEventListener:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) { }

它会在事务提交之后才触发。phase 有几个值:BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION。常用的就是 AFTER_COMMIT,保证数据已经落库了再干后续的事。

注意一点:@TransactionalEventListener 只有在有事务的情况下才会触发。如果发布事件的方法没标 @Transactional,监听器永远不执行。这个坑我踩过,方法上事务注解被误删了,监听器静默失效,排查了半天。要兜底的话可以加 fallbackExecution = true,没事务的时候也执行。

顺序问题

多个监听器监听同一个事件,默认顺序不确定。需要控制顺序的话加 @Order:

@Order(1)
@EventListener
public void addPoints(OrderCreatedEvent event) { }

@Order(2)
@EventListener
public void sendNotify(OrderCreatedEvent event) { }

数字小的先执行。不过我一般尽量避免让监听器之间有依赖,有依赖就说明它们不该拆成两个监听器。顺序这东西,加了之后以后加新监听器还得重新想一遍排第几,容易乱。

和 MQ 怎么选

进程内、不需要跨服务、丢了能接受(或者有补偿)的,用 Spring 事件。跨服务、需要持久化、需要重试的,上 MQ。

之前有同事拿 Spring 事件替代 MQ,服务重启的时候正在处理的事件全丢了,对账对不上。事件机制本身不持久化,进程挂了就没了。这个边界得清楚。

小结

Spring 事件解决的是“一个动作触发多个后续操作”的解耦问题,进程内用着很顺手。几个关键点:默认同步、事务场景用 @TransactionalEventListener 且注意它依赖事务、异步监听器得自己处理异常、别拿它替代 MQ。

先记这些。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。