👉这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事上“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 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 方法
四个核心角色
两种使用方式:接口实现 vs 注解
Spring 内置事件
同步还是异步?
@TransactionalEventListener:事务提交后再执行
源码解析:事件是怎么走到监听器的
踩坑实录
Spring Event vs MQ:怎么选?
生产级最佳实践
回到开头那个问题
问题:越写越胖的 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(事件)
数据载体。继承自 JDKEventObject,包含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 事件机制,正是实现这种协作的工具。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*)