Spring Event 事件机制

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()); // 当前线程:main
}

@EventListener
public void initUserWallet(UserRegisteredEvent event) {
System.out.println("3. 为用户创建钱包:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); // 当前线程:main
}
}


比较好的异步执行写法

自定义安全的线程池:

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
/**
* @EnableAsync 的本质是向 Spring 容器注入了 AsyncAnnotationBeanPostProcessor(Bean 后置处理器)。
* 加了 @EnableAsync,Spring 在创建 Bean 时,就会去检查里面有没有 @Async 方法。如果有,立刻用 CGLIB/JDK 动态代理把它包装起来,织入异步拦截逻辑。
*/
@Configuration
@EnableAsync // 使用 @Async 必须在启动类或配置类开启异步支持
public class EventListenerAsyncExecutor {

@Bean("eventExecutor") // 为线程池起一个明确的名字
public Executor eventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 1. 核心线程数
executor.setCorePoolSize(4);
// 2. 最大线程数
executor.setMaxPoolSize(8);
// 3. 阻塞队列容量(必须设为有界队列,防止 OOM)
executor.setQueueCapacity(500);
// 4. 线程名前缀(方便在日志和排查问题时识别)
executor.setThreadNamePrefix("event-async-");
// 5. 拒绝策略(队列满了且线程到了 max 后怎么处理)
// CallerRunsPolicy: 让调用者线程(主线程)去执行,既能起到限流降级作用,又不会丢任务
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()); // 当前线程:main
}

@Order(2)
@EventListener
@Async("eventExecutor") // 显式指定使用上面定义名为 eventExecutor 的线程池,这里的方法体整体都会在一个独立的异步线程中运行,不再需要手写 executor.execute(...)
public void initUserWallet(UserRegisteredEvent event) {
System.out.println("3. 为用户创建钱包:" + event.getUsername() + ",当前线程:" + Thread.currentThread().getName()); // 当前线程:event-async-1
}
}


原理是什么?

理解 Spring Event 的实现原理,关键在于搞清楚一个核心问题:Spring 是如何在容器启动时把 @EventListener 方法“收集”起来,并在调用 publishEvent() 时准确“找到”并执行它们的?本质上,Spring Event 的实现原理可以概括为三个核心步骤:组装广播器 -> 扫描并注册监听器 -> 事件匹配与反射调用。下面介绍这个原理三部曲。


第一,启动初始化:扫描并注册 @EventListener。当 Spring 容器启动时,EventListenerMethodProcessor 负责将声明式注解转化为监听器对象:

1
2
3
4
5
6
容器启动 (refresh)
└── 所有单例 Bean 实例化完成
└── # EventListenerMethodProcessor 遍历每一个 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):// 1. ApplicationContext 内部委托给广播器
public void publishEvent(Object event, ResolvableType eventType) {
// 获取系统的事件广播器(SimpleApplicationEventMulticaster)
getApplicationEventMulticaster().multicastEvent(event, eventType);
}

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));

// 2. 根据事件类型,匹配出所有合法的监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 3. 判断是否有异步线程池 Executor
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 {

// 内部类:保存 Bean 实例和 Method 的对应关系
private record ListenerMethodHolder(Object bean, Method method) {}

// 存储 事件类型 -> 监听器列表 的映射关系
// Key: 事件的 Class (比如 UserRegisteredEvent.class)
// Value: 包含 (Bean实例 + Method) 的包装对象
private final Map<Class<?>, List<ListenerMethodHolder>> listenerRegistry = new HashMap<>();


// 1. 扫描并注册 Bean 中的 @MyEventListener 方法
public void registerBean(Object bean) {
Class<?> clazz = bean.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyEventListener.class)) {
// 检查参数:EventListener 方法必须有且仅有一个参数(即事件对象)
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("@MyEventListener 方法必须有且仅有一个参数!");
}

Class<?> eventType = parameterTypes[0];
method.setAccessible(true); // 允许调用私有方法

// 注册到 Map 中
listenerRegistry.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(new ListenerMethodHolder(bean, method));
}
}
}

// 2. 发布事件:根据事件类型,找到对应的监听器并反射调用
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) {
// 1. 初始化事件广播器
MyEventMulticaster multicaster = new MyEventMulticaster();

// 2. 创建监听器 Bean,并注册进广播器 (模拟 Spring 容器启动扫描 Bean)
OrderNotifier orderNotifier = new OrderNotifier();
multicaster.registerBean(orderNotifier);

// 3. 发布事件 (模拟业务代码调用 publishEvent)
System.out.println(">>> 用户下单成功,开始发布 OrderCreatedEvent 事件...");
multicaster.publishEvent(new OrderCreatedEvent("ORD_20260731_001"));
}
}