spring event 默认同步执行,主线程需等待所有监听器串行执行完毕;启用异步须同时配置@enableasync、@async和@eventlistener,且推荐使用自定义线程池。

Spring Event 默认是同步执行,不加处理会卡住主线程
很多人一上来就写 @EventListener,发完事件发现接口响应变慢、超时、甚至线程池打满——因为 Spring Event 默认走的是同步调用链。发布一个 LoginEvent,所有监听器(发短信、写日志、推消息)都在同一个 HTTP 线程里串行执行,主线程得等全部跑完才返回。
这不是“解耦”,只是把代码从一个方法里拆到了多个类里,执行路径还是锁死的。
- 必须显式启用异步支持:在启动类加
@EnableAsync - 监听器方法上必须加
@Async,否则无效(仅@EventListener不够) -
@Async方法不能是 private / static,且不能在同一个类中自调用(代理失效) - 推荐为异步监听单独配置线程池,避免和 Web 请求线程池共用(比如用
taskExecutorBean)
@Async + @EventListener 组合写法最容易漏掉的三件事
光加两个注解还不够,实际运行中常因配置或写法问题导致异步失效,变成“假异步”。
- 没配
TaskExecutor:默认用SimpleAsyncTaskExecutor,每次新建线程,无复用、无上限,高并发下直接 OOM - 监听器类没被 Spring 扫描到:比如放在非
@ComponentScan路径下,或用了new XxxListener()手动实例化 - 事件对象是匿名内部类或 Lambda 构造的:Spring 无法识别泛型类型,
@EventListener匹配失败,监听器压根不触发
正确示例:
@Component
public class SmsNotificationListener {
@Async("notificationTaskExecutor") // 指向自定义线程池
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 这里才是真正异步执行的逻辑
sendSms(event.getOrder().getPhone());
}
}
事件对象要不要继承 ApplicationEvent?2026 年其实可以不继承
Spring 4.2+ 开始,@EventListener 支持纯 POJO 事件,无需继承 ApplicationEvent。但是否省略,取决于你是否需要事件源(source)信息或兼容老代码。
- 要获取发布者上下文(比如哪个 Service 发的)、或需兼容
ApplicationListener<t></t>接口写法 → 继承ApplicationEvent - 只做简单数据传递,且全用
@EventListener→ 直接传 DTO,如publishEvent(new OrderDTO(1001)) - 注意:用 POJO 事件时,监听方法参数类型必须和发布的对象类型完全一致(不能是父类),否则匹配失败
异步监听失败后怎么重试?别硬套 Spring Retry
@Retryable 不能直接加在 @Async + @EventListener 方法上——Spring AOP 代理链冲突,重试不会生效。
- 正确做法:把重试逻辑下沉到业务方法内,比如封装一个
sendSmsWithRetry()工具方法 - 或者用事件重发机制:监听器捕获异常后,手动调用
applicationEventPublisher.publishEvent()重新发一次(需加防重标识) - 更稳妥的做法是:关键通知类任务(如支付回调通知)仍走 MQ,Event 只用于非核心、允许丢失的场景(如统计埋点、缓存预热)
真正容易被忽略的是异常传播路径:异步监听里的异常默认被吞掉,连日志都不打。务必在 @Async 方法里加 try-catch + log.error,否则出问题根本不知道哪崩了。










