⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/bi-MQ-gengqingdeyibufanganSpring-neizhidezheigeyinzanggongnenghenduorenhaibuzhidao/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

问题:越写越胖的 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事件机制核心组件全景:事件、发布者、广播器、监听者四大角色的关系图

1. ApplicationEvent(事件)

数据载体。继承自 JDK EventObject,包含 source 和 timestamp。Spring 4.2 后支持发布任意对象(不继承也行),内部自动封装为 PayloadApplicationEvent。

2. ApplicationEventPublisher(发布者)

只有一个方法 publishEvent()。ApplicationContext 实现了这个接口,注入 ApplicationEventPublisher 更优雅——依赖接口而非实现。

3. ApplicationEventMulticaster(广播器)

整个机制的核心调度引擎。维护所有已注册的监听器列表,负责将事件分发给匹配的监听者。默认实现 SimpleApplicationEventMulticaster。

4. ApplicationListener(监听者)

接收并处理事件,通过实现接口或注解定义。

角色 对应组件 责任
事件 ApplicationEvent 信息载体
发布者 ApplicationEventPublisher 触发事件
广播器 ApplicationEventMulticaster 调度分发
监听者 ApplicationListener / @EventListener 处理事件

两种使用方式:接口实现 vs 注解

方式一:实现 ApplicationListener 接口(传统方式)

Step 1:定义事件对象

public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
private final Long userId;
private final 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
public class OrderService {
private final 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
public class SmsNotifyListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
smsService.send(event.getUserId(), "订单 " + event.getOrderId() + " 已提交");
}
}

@Component
public class PointsListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
pointService.addPoints(event.getUserId(), event.getTotalAmount());
}
}

缺点:一个监听器只能监听一种事件类型,类里只能有一个处理方法。

方式二:@EventListener 注解(推荐)

Spring 4.2 引入,灵活度大幅提升,主流写法:

@Component
public class 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) {
return new 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 在容器生命周期关键节点也会发布事件:

Spring内置事件类型列表

事件 触发时机 常用场景
ContextRefreshedEvent 容器初始化或刷新完成 预热缓存、加载配置
ContextStartedEvent 调用 context.start() 启动定时任务
ContextStoppedEvent 调用 context.stop() 清理资源
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 对比选型决策树

对比维度 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
public class NewFeatureListener {
@EventListener
public void handleNewFeature(OrderCreatedEvent event) {
// 新功能在这里
}
}

一个经得起考验的系统,不是靠把所有逻辑塞进一个方法里,而是让不同模块各司其职。Spring 事件机制,正是实现这种协作的工具。

文章目录
  1. 1. 问题:越写越胖的 Service 方法
  2. 2. 四个核心角色
  3. 3. 两种使用方式:接口实现 vs 注解
    1. 3.0.1. 方式一:实现 ApplicationListener 接口(传统方式)
    2. 3.0.2. 方式二:@EventListener 注解(推荐)
  • 4. Spring 内置事件
  • 5. 同步还是异步?
    1. 5.0.1. 开启异步
    2. 5.0.2. 两个必知的异步局限
  • 6. @TransactionalEventListener:事务提交后再执行
  • 7. 源码解析:事件是怎么走到监听器的
    1. 7.0.1. 注册阶段
    2. 7.0.2. 分发阶段
  • 8. 踩坑实录
    1. 8.0.1. 坑 1:事件对象设计成可变的
    2. 8.0.2. 坑 2:开了 @Async 却没开 @EnableAsync
    3. 8.0.3. 坑 3:事件循环调用
    4. 8.0.4. 坑 4:@TransactionalEventListener 无事务时默默丢事件
  • 9. Spring Event vs MQ:怎么选?
  • 10. 生产级最佳实践
  • 11. 回到开头那个问题