1089 字
约 3 分钟
7
S07-J03 · Spring Event:事件发布订阅
适用读者:想解耦"下单后要发短信/加积分/写日志"这类逻辑的人。
前置:S07-B02、S07-J02。
实测环境:Spring Boot 3.3.6 / JDK 17(2026-11-05)。
覆盖旧文:634(469) 386(144) = 613 点。
覆盖扩展:E-S07-015(同步 vs 异步、事务边界)。
上一篇:S07-J02 · 循环依赖与三级缓存
下一篇:S07-J04 · 定时任务与异步。
1. 一句话结论
默认事件是同步的:publishEvent 会在当前线程把监听器全部执行完才返回。要异步必须显式 @Async + 线程池。
实测输出(顺序即事实):
[1] 发布事件前
[2] 同步监听: A100
[3] 发布事件后
[async] 异步监听: A100 线程=task-1
读法:
| 观察 | 结论 |
|---|---|
[2] 在 [3] 之前 |
同步监听阻塞了发布方 |
[async] 在 [3] 之后、线程是 task-1 |
异步监听在独立线程池执行 |
2. 三个组件
| 组件 | 作用 |
|---|---|
ApplicationEventPublisher |
发布(publishEvent) |
@EventListener |
监听(方法参数即事件类型) |
| 事件类 | 任意 POJO(Boot 4.2+/Spring 4.2+ 不强制继承 ApplicationEvent) |
record OrderPaid(String id) {}
@Component class Listener {
@EventListener void on(OrderPaid e) { /* 同步 */ }
@Async @EventListener void onAsync(OrderPaid e) { /* 异步 */ }
}
3. 同步 vs 异步(E-S07-015)
| 维度 | 同步(默认) | 异步(@Async) |
|---|---|---|
| 线程 | 发布方线程 | 线程池(task-N) |
| 发布方是否等待 | 等待 | 不等 |
| 异常传播 | 会抛回发布方 | 被 AsyncUncaughtExceptionHandler 接 |
| 事务边界 | 同一事务(可参与回滚) | 不在同一事务 |
| 适用 | 强一致(如扣库存) | 最终一致(发短信、加积分) |
判据:"这段逻辑失败要不要连累主流程?" 要 → 同步;不要 → 异步(但要接受"可能失败")。
4. 事务边界:最容易踩的坑(A-386)
问题:在事务方法里 publishEvent,同步监听器会在事务提交前执行——监听器里查不到未提交的数据。
| 方案 | 做法 |
|---|---|
@TransactionalEventListener(phase = AFTER_COMMIT) |
提交后再执行(推荐) |
@TransactionalEventListener(phase = BEFORE_COMMIT) |
提交前(能参与回滚) |
| 异步 + 事务 | 注意"事件发出但事务回滚"导致的不一致 |
判据:"监听器需要读到本次事务的写结果吗?" 需要 → AFTER_COMMIT。
5. 事件机制的适用边界
| 适合 | 不适合 |
|---|---|
| 主流程不关心结果的副作用(通知、统计) | 必须与主流程同生共死的逻辑(用同步调用) |
| 跨模块解耦(订单 → 积分/消息) | 强顺序、强一致的核心链路 |
| 替代"反向依赖"(J02 的循环依赖) | 高频大数据量(进程内事件不适合做消息队列) |
与 MQ 的区别:Spring Event 是进程内的,应用重启/多实例时不会跨进程投递——需要可靠性就用 MQ(S17)。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "事件默认是异步的" | 默认同步(实测 [2] 阻塞 [3]) |
| "异步监听会参与事务" | 不在同一事务 |
| "事件能替代 MQ" | 只在本进程内 |
| "@EventListener 能保证顺序" | 同步按注册顺序;异步不保证 |
| "监听器异常不会影响主流程" | 同步监听器异常会抛回发布方 |
7. 自检
- 默认事件是同步还是异步?实测怎么证明?
@Async监听器在哪个线程执行?异常谁处理?- 为什么要在
AFTER_COMMIT阶段监听? - Spring Event 与 MQ 的本质区别?
- 哪类逻辑不该用事件?
8. 下一篇
S07-J04 · 定时任务与异步:@Scheduled / 线程池 / 重试
事件讲完,下一篇讲"按时间触发":默认调度器只有一个线程(实测 scheduling-1),以及重试组件的设计。
本篇属于系列《S07 · Spring 框架》。旧文来源:A-634/386;扩展点 E-S07-015。同步/异步顺序与线程名为 Spring Boot 3.3.6 实测。
S07-J03 · Spring Event:事件发布订阅
https://www.clxhxhhr.top/posts/5276/ 评论
0 条
还没有评论,先写一条吧。