比 MQ 更轻的异步方案:Spring 内置的这个隐藏功能,很多人还不知道
👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事上“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
《精进 Java 学习指南》:系统学习,互联网主流技术栈
《必读 Java 源码专栏》:知其然,知其所以然

👉这是一个或许对你有用的开源项目
国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构
RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能:
多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro
微服务:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本
问题:越写越胖的 Service 方法
工作年头长了,见过太多这样的 Service 方法:
public void createOrder(Order order) {
// 1. 保存订单
orderDao.save(order);
// 2. 发送短信通知
smsService.sendOrderConfirm(order.getUserPhone(), order.getId());
// 3. 更新用户积分
pointService.addPoints(order.getUserId(), order.getTotalAmount());
// 4. 记录操作日志
auditService.log("CREATE_ORDER", order.getId());
// 5. 推送站内消息
notifyService.push(order.getUserId(), "您的订单已提交");
// 6. 触发风控检查
riskService.check(order);
}
技术上没 Bug,跑得也好好的。但有一天产品说"下单后还要同步购物车",你得改;运营说"VIP 下单要额外赠券",你还得改。核心下单逻辑就这样被各种非核心需求越填越厚,所有依赖硬编码在一起。哪天 riskService 挂了,整个下单都跟着挂 ——这明显不合理。
很多人的第一反应是上 MQ。但如果是单体应用,或者只是同 JVM 内的逻辑解耦 ,部署一套 MQ 中间件有点杀鸡用牛刀。
Spring 早就提供了一套轻量级事件驱动机制 ——ApplicationEvent + @EventListener,本质是发布-订阅 模式,零额外依赖,Spring Boot 开箱即用。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
四个核心角色
1. ApplicationEvent(事件)
数据载体。继承自 JDK EventObject,包含 source 和 timestamp。Spring 4.2 后支持发布任意对象(不继承也行),内部自动封装为 PayloadApplicationEvent。
2. ApplicationEventPublisher(发布者)
只有一个方法 publishEvent()。ApplicationContext 实现了这个接口,注入 ApplicationEventPublisher 更优雅——依赖接口而非实现 。
3. ApplicationEventMulticaster(广播器)
整个机制的核心调度引擎 。维护所有已注册的监听器列表,负责将事件分发给匹配的监听者。默认实现 SimpleApplicationEventMulticaster。
4. ApplicationListener(监听者)
接收并处理事件,通过实现接口或注解定义。
|
角色 |
对应组件 |
责任 |
|---|---|---|
|
事件 |
ApplicationEvent |
信息载体 |
|
发布者 |
ApplicationEventPublisher |
触发事件 |
|
广播器 |
ApplicationEventMulticaster |
调度分发 |
|
监听者 |
ApplicationListener
/ |
处理事件 |
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
两种使用方式:接口实现 vs 注解
方式一:实现 ApplicationListener 接口(传统方式)
Step 1:定义事件对象
public class OrderCreatedEvent extends ApplicationEvent {
privatefinal String orderId;
privatefinal Long userId;
privatefinal BigDecimal totalAmount;
public OrderCreatedEvent(Object source, String orderId, Long userId, BigDecimal totalAmount) {
super(source);
this.orderId = orderId;
this.userId = userId;
this.totalAmount = totalAmount;
}
public String getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public BigDecimal getTotalAmount() { return totalAmount; }
}
Step 2:发布事件
@Service
publicclass OrderService {
privatefinal ApplicationEventPublisher eventPublisher;
public OrderService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public Order createOrder(CreateOrderRequest req) {
Order order = buildOrder(req);
orderDao.save(order);
eventPublisher.publishEvent(
new OrderCreatedEvent(this, order.getId(), order.getUserId(), order.getTotalAmount())
);
return order;
}
}
Step 3:实现监听器
@Component
publicclass SmsNotifyListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
smsService.send(event.getUserId(), "订单 " + event.getOrderId() + " 已提交");
}
}
@Component
publicclass PointsListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
pointService.addPoints(event.getUserId(), event.getTotalAmount());
}
}
缺点:一个监听器只能监听一种事件类型 ,类里只能有一个处理方法。
方式二:@EventListener 注解(推荐)
Spring 4.2 引入,灵活度大幅提升,主流写法:
@Component
publicclass OrderEventHandler {
@EventListener
public void handleSms(OrderCreatedEvent event) {
smsService.send(event.getUserId(), "订单已提交");
}
// 同时监听多种事件
@EventListener(value = {OrderCreatedEvent.class, OrderPaidEvent.class})
public void handleOrderStateChange(ApplicationEvent event) {
auditService.log(event);
}
// 条件监听:只处理金额 > 1000 元的
@EventListener(condition = "#event.totalAmount > 1000")
public void handleHighValueOrder(OrderCreatedEvent event) {
vipService.notifyHighValue(event.getOrderId());
}
// 返回新事件:自动广播下一个
@EventListener
public InventoryUpdateEvent handleOrder(OrderCreatedEvent event) {
returnnew InventoryUpdateEvent(this, event.getOrderId());
}
}
几个少被提及的功能:**condition** 支持 SpEL 过滤;返回值如果是事件对象会自动发布,适合事件链;@Order 控制执行顺序。
@EventListener
@Order(1) // 先执行
@Async
public void riskCheck(OrderCreatedEvent event) { ... }
@EventListener
@Order(2) // 后执行
@Async
public void sendSms(OrderCreatedEvent event) { ... }
Spring 内置事件
Spring 在容器生命周期关键节点也会发布事件:
|
事件 |
触发时机 |
常用场景 |
|---|---|---|
ContextRefreshedEvent |
容器初始化或刷新完成 |
预热缓存、加载配置 |
ContextStartedEvent |
调用 |
启动定时任务 |
ContextStoppedEvent |
调用 |
清理资源 |
ContextClosedEvent |
容器关闭 |
优雅关机 |
RequestHandledEvent |
HTTP 请求处理完毕 |
请求监控 |
最常用的是 ContextRefreshedEvent,预热缓存 的经典用法:
@Component
public class CachePreloadListener {
@EventListener(ContextRefreshedEvent.class)
public void preloadCache(ContextRefreshedEvent event) {
// 父子容器场景会触发两次,需要防守
if (event.getApplicationContext().getParent() != null) {
return;
}
CompletableFuture.runAsync(() -> {
provinceService.loadAll();
hotProductService.preload();
log.info("缓存预热完成");
});
}
}
Spring Boot 里有更好用的 ApplicationReadyEvent,只触发一次:
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
cacheService.initAll();
}
同步还是异步?
Spring 事件默认同步 ——publishEvent() 会阻塞,等所有监听器执行完才继续。好处是简单、对事务友好;坏处是监听器卡顿会拖慢发布者 。
开启异步
三步搞定:
第一步:开启异步支持
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第二步:配置专用线程池 (强烈建议,否则会用 ForkJoinPool.commonPool)
@Configuration
public class AsyncConfig {
@Bean("eventExecutor")
public Executor eventTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(16);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("event-async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
第三步:给监听器加 @Async
@Async("eventExecutor")
@EventListener
public void handleSms(OrderCreatedEvent event) {
smsService.send(event.getUserId(), "订单已提交");
}
两个必知的异步局限
1. 异常不传播 :异步监听器的异常在线程池线程中自行消化,发布者完全感知不到。必须在监听器内 try-catch。
2. 返回值发布失效 :加了 @Async 后,返回事件对象不会自动发布,需手动 publishEvent。
@Async("eventExecutor")
@EventListener
public void handleSms(OrderCreatedEvent event) {
try {
smsService.send(event.getUserId(), "订单已提交");
} catch (Exception e) {
log.error("短信发送失败,订单 ID: {}", event.getOrderId(), e);
alarmService.notify("SMS_FAILED", e.getMessage());
}
}
@TransactionalEventListener:事务提交后再执行
场景:用户修改订单后删缓存。如果用普通 @EventListener,缓存删了但事务可能回滚——缓存击穿 。
@TransactionalEventListener 解决这个问题:
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void cleanCache(OrderUpdatedEvent event) {
redisTemplate.delete("order:" + event.getOrderId());
}
|
phase |
触发时机 |
|---|---|
AFTER_COMMIT
(默认) |
事务成功提交后 |
AFTER_ROLLBACK |
事务回滚后 |
AFTER_COMPLETION |
事务完成后(无论成败) |
BEFORE_COMMIT |
事务提交前 |
两个隐形陷阱 :
1. 没有事务时事件会被丢弃
// 没有 @Transactional 的方法发布事件
public void someMethod() {
eventPublisher.publishEvent(new OrderUpdatedEvent(...));
// @TransactionalEventListener 会直接丢弃!
}
加 fallbackExecution = true 解决:
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void cleanCache(OrderUpdatedEvent event) {
redisTemplate.delete("order:" + event.getOrderId());
}
2. AFTER_COMMIT 阶段里起不了新事务
事务已提交,加了 @Transactional 默认也不会新开事务,必须指定 Propagation.REQUIRES_NEW。
源码解析:事件是怎么走到监听器的
注册阶段
容器 refresh() 过程中完成两件事:
初始化广播器 :
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster = beanFactory.getBean(...);
} else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
}
}
注册监听器 :先注册编程式添加的监听器,再从 BeanFactory 找所有 ApplicationListener 接口的 Bean。@EventListener 注解由 EventListenerMethodProcessor 处理——在所有单例 Bean 初始化完成后扫描带注解的方法,封装为 ApplicationListenerMethodAdapter 注册到广播器。
分发阶段
// SimpleApplicationEventMulticaster#multicastEvent()
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
getApplicationListeners() 内部有缓存,首次调用遍历所有监听器做类型匹配,之后走缓存。
完整调用链:
publishEvent()
└─ multicastEvent()
└─ getApplicationListeners()(带缓存的匹配)
└─ invokeListener()
└─ doInvokeListener()
└─ listener.onApplicationEvent() ← 你的业务代码
踩坑实录
坑 1:事件对象设计成可变的
@EventListener
public void handleA(OrderCreatedEvent event) {
event.setStatus("PROCESSING"); // 修改了事件对象
}
@EventListener
public void handleB(OrderCreatedEvent event) {
// handleA 已经改了 status,这里看到脏数据
doSomething(event.getStatus());
}
多个监听器共享同一个事件对象。事件对象必须不可变 :属性加 final,不提供 setter。
坑 2:开了 @Async 却没开 @EnableAsync
@EventListener
@Async
public void asyncHandle(OrderCreatedEvent event) {
// 看起来异步,实际同步执行,而且无任何报错
}
@Async 不生效的表现是性能没提升却找不到原因。**@EnableAsync 必须加在主类或配置类上。**
坑 3:事件循环调用
@EventListener
public void handleA(EventA event) {
eventPublisher.publishEvent(new EventB());
}
@EventListener
public void handleB(EventB event) {
eventPublisher.publishEvent(new EventA()); // 死循环!StackOverflowError
}
设计事件时要明确依赖关系,避免形成环。
坑 4:@TransactionalEventListener 无事务时默默丢事件
上面已经提到。没事务 + 没设 fallbackExecution = true = 事件被默默丢弃,调试时极难发现。
Spring Event vs MQ:怎么选?
|
对比维度 |
Spring Event |
MQ(Kafka/RocketMQ) |
|---|---|---|
|
适用场景 |
同 JVM 内模块解耦 |
跨服务通信 |
|
可靠性 |
进程崩溃就丢事件 |
持久化 + 重试 |
|
延迟 |
内存级,≈ 0 |
受网络影响 |
|
开发成本 |
零依赖,注解即用 |
需部署中间件 |
|
一致性 |
本地事务保障 |
需分布式事务 |
|
吞吐量 |
异步+批量 ~9w/s |
受网络并发限制 |
简单说:单体应用 / 同 JVM 逻辑解耦 / 不要求消息持久化 → Spring Event ;微服务跨服务通信 / 高可靠消息传输 → MQ 。
生产级最佳实践
1. 事件对象保持轻量 :只传 ID,不传大对象。
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
private final String version = "1.0";
}
2. 一个监听器只做一件事 :PaymentListener 只处理支付,CouponListener 只发券。
3. 异步监听器必须自己捕异常 。
4. 加监控 :关键链路的监听器加耗时统计和告警。
5. 事件命名要语义化 :用过去式动词——OrderCreatedEvent、PaymentSuccessEvent。不要用 DoSomethingEvent。
回到开头那个问题
那个越写越胖的 createOrder,用事件机制改造:
@Transactional
public Order createOrder(CreateOrderRequest req) {
Order order = buildOrder(req);
orderDao.save(order);
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
return order;
}
// 新增功能?加个监听器就好,不动核心代码
@Component
publicclass NewFeatureListener {
@EventListener
public void handleNewFeature(OrderCreatedEvent event) {
// 新功能在这里
}
}
一个经得起考验的系统,不是靠把所有逻辑塞进一个方法里,而是让不同模块各司其职。Spring 事件机制,正是实现这种协作的工具。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。





文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
更多推荐



所有评论(0)