Spring事件默认同步执行,不加@Async会阻塞主线程;UserCreatedEvent发布后注册失败因SimpleApplicationEventMulticaster同步分发,异常导致事务回滚;需配置线程池并加@Async实现真解耦。

能解耦,但默认是同步执行的,不加 @Async 就会卡住主线程,核心业务得等所有监听器跑完才返回——这不是真解耦。
为什么 UserCreatedEvent 发布后日志还没写完,用户注册就失败了?
因为 Spring 默认用 SimpleApplicationEventMulticaster 同步分发事件:发布线程(比如 HTTP 请求线程)会依次调用每个 @EventListener 方法,中间任何一个抛异常、超时或阻塞,都会导致整个事务回滚或响应失败。
- 常见错误现象:
UserService.createUser()报NullPointerException或超时,但查数据库发现用户已存入,说明事件监听器出错了,而事务没按预期提交 - 根本原因:没配异步执行器,
publishEvent()实际是“同步广播” - 必须显式配置线程池,并在监听方法上加
@Async,否则和直接调用服务没区别 - 注意:
@Async要求类被 Spring 管理(@Service),且不能在同一个类里自调用(this.method() 不生效)
@EventListener 和 ApplicationListener 选哪个?
优先用 @EventListener。它更轻量、支持泛型推导、可配合 @Async 和 SpEL 条件表达式(比如 @EventListener(condition = "#event.operationType == 'USER_REGISTRATION'")),而 ApplicationListener 接口需要泛型声明且无法直接异步。
-
@EventListener方法签名灵活:参数可以是事件本身、Object、甚至提取字段(如@Payload User user) -
ApplicationListener必须实现onApplicationEvent(T event),泛型T写死,改类型就得改类 - 两者都支持
@Order控制执行顺序,但@EventListener更易维护
事务内发布事件,监听器能读到刚插入的数据吗?
能,但仅限于监听器**同步执行**且**在同一个事务上下文**中。一旦加了 @Async,监听器就在新线程、新事务里运行,默认读不到未提交的数据(除非数据库隔离级别设为 READ_UNCOMMITTED,不推荐)。
- 典型陷阱:异步监听器调用
userRepository.findById()查不到刚创建的用户,因为主事务还没提交 - 解决方案一:监听器不查库,改用事件携带的完整
User对象(如UserCreatedEvent中的user字段) - 解决方案二:监听器用
@Transactional(propagation = Propagation.REQUIRES_NEW)开新事务,但需确保主事务已提交(即事件在@Transactional方法末尾发布) - 关键点:
publishEvent()必须放在@Transactional方法的最后,否则可能触发“事务未完成就发事件”
事件对象该不该继承 ApplicationEvent?
Spring 4.2+ 支持 POJO 事件(不用继承),但**仍建议继承**,尤其在需要时间戳、事件源溯源或兼容老版本时。
- 继承
ApplicationEvent自动带timestamp和source,调试时能快速定位事件从哪个 Bean 发出 - 某些监控工具(如 Spring Boot Admin)依赖
ApplicationEvent类型做事件分类 - POJO 事件虽简洁,但丢失了事件元数据,排查
OrderPaidEvent是谁发的、什么时候发的会变困难 - 如果用 Lombok,
@Getter+ 显式调用super(source)就够,不增加多少代码量
真正容易被忽略的是事件生命周期和事务边界的咬合点:不是加了 @Async 就万事大吉,而是得确认事件发布时间、事务提交时机、监听器事务传播方式三者对齐,否则解耦反而引入数据不一致风险。










