Spring Event 简介
解决的问题
在软件开发中,我们经常会遇到这样的业务场景:当一个主业务完成后,需要触发一连串的后续操作。例如,用户成功注册账号后,系统需要:
- 给用户发送欢迎邮件;
- 为用户发放新手优惠券;
- 初始化用户的积分账户;
- 记录注册日志。
如果直接把这些代码全写在 registerUser() 方法里,主业务方法就会变得极其臃肿,而且每当要增加新功能(比如“发送注册短信通知”)时,都必须修改原有的注册逻辑,这严重违反了设计模式中的开闭原则(对扩展开放,对修改关闭)。而 Spring 事件监听机制(Spring Event) 就是为了解决这个问题而生的。
Spring 事件机制
Spring 事件机制本质上是设计模式中 “观察者模式”(Observer Pattern)在 Spring 框架中的实现与增强。它提供了一种解耦(Decoupling)的进程内通信方式。在这个机制里,系统被划分为三个核心角色:
1
| [ 事件源 (Event) ] ──(携带数据)──> [ 发布者 (Publisher) ] ──(广播)──> [ 监听器 (Listener) ]
|
- 事件(Event):事件的载体,本质是一个普通的 Java 对象(POJO),用于存放被广播的数据(如 UserRegisteredEvent)。
- 发布者(Publisher):负责将事件“广播”出去的角色,调用 ApplicationEventPublisher.publishEvent() 方法。
- 监听器(Listener):负责“接收并处理”事件的角色,使用 @EventListener 注解标记在方法上。
通过这种方式,主业务(发布者)只需要关心“把事情发生了这个消息广播出去”,而不需要关心“谁会来处理这个消息”。
为什么要用它
- 代码高度解耦:注册逻辑只负责注册,发送邮件和发放优惠券的代码各自独立在自己的类里,互不干扰。
- 极强的可扩展性:如果未来要新增“统计注册数据”的功能,只需新建一个监听器类加上 @EventListener 即可,一行原有的主业务代码都不用改。
- 开箱即用,无需第三方依赖:相比于引入 Kafka、RabbitMQ 等轻量级或重量级消息队列,Spring Event 是 Spring 框架原生自带的,零配置成本,非常适合单体应用内部的业务解耦。
简单案例
定义事件(Event)
定义一个普通的 Java 类来作为事件载体:
1 2 3 4 5 6
| @Data @AllArgsConstructor @NoArgsConstructor public class UserRegisteredEvent { private String username; }
|
发布事件(Publisher)
在主业务 Bean 中注入 ApplicationEventPublisher 并发布事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import jakarta.annotation.Resource; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service;
@Service public class UserService {
@Resource private ApplicationEventPublisher eventPublisher;
public void registerUser(String username) { System.out.println("1. 用户注册成功:" + username); eventPublisher.publishEvent(new UserRegisteredEvent(username)); } }
|
监听事件(Listener)
在处理后续逻辑的 Bean 方法上贴上 @EventListener 注解:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class NotificationListener {
@EventListener public void sendWelcomeEmail(UserRegisteredEvent event) { System.out.println("2. 发送欢迎邮件给:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); }
@EventListener public void initUserWallet(UserRegisteredEvent event) { System.out.println("3. 为用户创建钱包:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); } }
|
比较好的异步执行写法
自定义安全的线程池:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
@Configuration @EnableAsync public class EventListenerAsyncExecutor {
@Bean("eventExecutor") public Executor eventExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(500); executor.setThreadNamePrefix("event-async-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize(); return executor; } }
|
修改 NotificationListener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Component public class NotificationListener {
@Order(1) @EventListener public void sendWelcomeEmail(UserRegisteredEvent event) { System.out.println("2. 发送欢迎邮件给:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); }
@Order(2) @EventListener @Async("eventExecutor") public void initUserWallet(UserRegisteredEvent event) { System.out.println("3. 为用户创建钱包:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); } }
|
原理是什么?
理解 Spring Event 的实现原理,关键在于搞清楚一个核心问题:Spring 是如何在容器启动时把 @EventListener 方法“收集”起来,并在调用 publishEvent() 时准确“找到”并执行它们的?本质上,Spring Event 的实现原理可以概括为三个核心步骤:组装广播器 -> 扫描并注册监听器 -> 事件匹配与反射调用。下面介绍这个原理三部曲。
第一,启动初始化:扫描并注册 @EventListener。当 Spring 容器启动时,EventListenerMethodProcessor 负责将声明式注解转化为监听器对象:
1 2 3 4 5 6
| 容器启动 (refresh) └── 所有单例 Bean 实例化完成 └── └── 检查方法上是否有 @EventListener 注解 └── 封装为 ApplicationListenerMethodAdapter (适配器) └── 注册进 ApplicationEventMulticaster 的 Listener 注册表中
|
第二,发布事件:publishEvent() 做了什么?当你在代码中调用 applicationContext.publishEvent(event) 时,发生了以下调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 反射触发 (doInvoke): public void publishEvent(Object event, ResolvableType eventType) { getApplicationEventMulticaster().multicastEvent(event, eventType); }
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
|
第三,事件匹配与执行:如何精准找到对应的监听器?getApplicationListeners(event, type) 是匹配的核心,Spring 在这里做了高效的处理:
- 类型匹配 (supportsEventType):Spring 会判断当前事件的 Class 类型(例如 UserRegisteredEvent)是否可以赋值给监听器声明的参数类型(listener.supportsEventType(type))。这里支持多态与继承,如果监听器监听的是 BaseEvent,那么子类 UserRegisteredEvent 也能被匹配到。
- 条件表达式过滤 (condition):如果 @EventListener(condition = “#event.age > 18”) 配置了 SpEL 表达式,Spring 会在此时解析 SpEL,只有表达式返回 true 的监听器才会被选中。
- 性能缓存 (Cache):每次匹配非常耗时,因此 Spring 内部使用 ConcurrentHashMap 缓存了 [Event Class -> List<ApplicationListener>] 的映射关系。第二次发布同类事件时,直接查缓存,毫秒级响应。
- 反射触发 (doInvoke):匹配成功后,ApplicationListenerMethodAdapter 通过 method.invoke(targetBean, event) 执行具体的业务方法。
总结流程全景,将 Spring Event 的底层实现提炼为一张小图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌────────────────────────────────────────────────────────────┐ │ 1. 启动阶段 (Scan & Register) │ │ Bean 实例化 ──> 扫描 @EventListener │ │ ──> 包装为 ApplicationListenerAdapter │ │ ──> 注册到 SimpleApplicationEventMulticaster│ └────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────┐ │ 2. 运行阶段 (Publish & Match) │ │ publishEvent(event) ──> 查 Multicaster 缓存 │ │ ──> 类型匹配 / SpEL条件过滤 │ │ ──> 按 @Order 重新排序 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────┐ │ 3. 执行阶段 (Invoke) │ │ 无 @Async ──> 当前线程反射调用 method.invoke() │ │ 有 @Async ──> 提交至 ThreadPoolTaskExecutor 异步执行 │ └─────────────────────────────────────────────────────────┘
|
手撕一个出来
框架模拟代码
MyEventListener
1 2 3 4
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyEventListener { }
|
MyEventMulticaster
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class MyEventMulticaster {
private record ListenerMethodHolder(Object bean, Method method) {}
private final Map<Class<?>, List<ListenerMethodHolder>> listenerRegistry = new HashMap<>();
public void registerBean(Object bean) { Class<?> clazz = bean.getClass(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(MyEventListener.class)) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length != 1) { throw new IllegalArgumentException("@MyEventListener 方法必须有且仅有一个参数!"); }
Class<?> eventType = parameterTypes[0]; method.setAccessible(true);
listenerRegistry.computeIfAbsent(eventType, k -> new ArrayList<>()) .add(new ListenerMethodHolder(bean, method)); } } }
public void publishEvent(Object event) { Class<?> eventType = event.getClass(); List<ListenerMethodHolder> holders = listenerRegistry.get(eventType);
if (holders != null) { for (ListenerMethodHolder holder : holders) { try { holder.method.invoke(holder.bean, event); } catch (Exception e) { e.printStackTrace(); } } } } }
|
测试验证代码
OrderCreatedEvent
1 2 3 4 5
| @Data @AllArgsConstructor public class OrderCreatedEvent { private String orderId; }
|
OrderNotifier
1 2 3 4 5 6 7 8 9 10 11 12
| public class OrderNotifier {
@MyEventListener public void sendSms(OrderCreatedEvent event) { System.out.println("【短信服务】收到新订单:" + event.getOrderId() + ",正在发送短信通知..."); }
@MyEventListener public void reduceInventory(OrderCreatedEvent event) { System.out.println("【库存服务】收到新订单:" + event.getOrderId() + ",正在扣减库存..."); } }
|
Main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Main {
public static void main(String[] args) { MyEventMulticaster multicaster = new MyEventMulticaster();
OrderNotifier orderNotifier = new OrderNotifier(); multicaster.registerBean(orderNotifier);
System.out.println(">>> 用户下单成功,开始发布 OrderCreatedEvent 事件..."); multicaster.publishEvent(new OrderCreatedEvent("ORD_20260731_001")); } }
|