一,什么是 Spring Event?
Spring Event 是 Spring 框架提供的一种事件驱动编程的实现,它基于经典的观察者模式。简单来说,就是当系统中某个对象的状态发生变化时,它会发布一个“事件”,而其他对这个事件感兴趣的对象(监听器)就会收到通知并执行相应的处理。
这种机制的好处是解耦:事件的发布者和处理者不需要直接相互依赖,从而让代码更加灵活、易于扩展。比如:
- 用户注册成功后,需要发送欢迎邮件、赠送积分、记录日志等。
- 订单支付完成后,需要更新库存、发送短信通知、生成财务流水等。
如果不使用事件,你可能会在注册服务里依次调用邮件、积分、日志服务,导致代码臃肿且耦合。使用事件后,注册服务只需发布一个“用户注册成功事件”,然后由各自的监听器去处理后续逻辑,注册服务本身可以专注于核心业务。
二,Spring Event 的核心组件
Spring 事件机制主要由三个部分组成:
- 事件(Event)
继承自ApplicationEvent的普通 Java 对象,用来封装事件中携带的数据。例如UserRegisteredEvent可以包含用户信息。 - 监听器(Listener)
对特定事件做出响应的组件。当事件被发布后,符合条件的监听器会被触发执行。Spring 提供了两种定义监听器的方式:- 实现
ApplicationListener接口 - 使用
@EventListener注解(更推荐,更简洁)
- 实现
- 发布器(Publisher)
负责发布事件的对象,通常是ApplicationEventPublisher接口的实现(Spring 容器会自动注入)。你只需要在需要发布事件的地方注入ApplicationEventPublisher,然后调用publishEvent方法即可。
三,基础用法
3.1 准备工作
确保你的项目已经引入了 Spring 框架(Spring Boot 最方便)。如果是 Spring Boot,默认已经包含了所需依赖。
3.2 定义事件类
创建一个事件类,继承 ApplicationEvent(也可以不继承,Spring 4.2+ 支持任意对象作为事件,但通常继承以便于区分)。事件类可以包含你需要传递的数据。
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
private final String email;
public UserRegisteredEvent(Object source, String username, String email) {
super(source);
this.username = username;
this.email = email;
}
// getters
public String getUsername() { return username; }
public String getEmail() { return email; }
}
注意:
source通常是发布事件的对象(比如UserService),也可以传入 null,但建议传入有意义的来源。
3.3 创建监听器
我们可以用两种方式定义监听器:
方式一 :实现 ApplicationListener 接口
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class EmailNotificationListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
System.out.println("发送欢迎邮件给:" + event.getEmail());
// 实际发送邮件的代码...
}
}
方式二: 使用 @EventListener 注解(推荐)
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class UserEventListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
System.out.println("发送欢迎邮件给:" + event.getEmail());
}
// 也可以定义多个监听方法处理同一个事件,或者处理不同事件
@EventListener
public void handleAnother(UserRegisteredEvent event) {
System.out.println("记录注册日志:" + event.getUsername());
}
}
使用注解的方式更加灵活,一个类里可以定义多个监听方法,而且方法名可以自定义。
3.4 发布事件
在需要发布事件的地方(例如用户注册的业务逻辑中),注入 ApplicationEventPublisher 并调用 publishEvent。
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void registerUser(String username, String email) {
// 1. 保存用户到数据库
System.out.println("保存用户:" + username);
// 2. 发布事件
UserRegisteredEvent event = new UserRegisteredEvent(this, username, email);
eventPublisher.publishEvent(event);
}
}
3.5 测试
创建一个简单的启动类或测试类,调用 registerUser 方法,你会看到监听器中的输出。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserService userService = context.getBean(UserService.class);
userService.registerUser("张三", "zhangsan@example.com");
}
}
输出:
保存用户:张三
发送欢迎邮件给:zhangsan@example.com
记录注册日志:张三
发送欢迎邮件给:zhangsan@example.com
四,同步与异步
默认情况下,Spring 事件是同步的:发布事件后,发布者线程会阻塞等待所有监听器执行完毕,然后才继续往下执行。如果监听器中有耗时操作(如发送邮件、调用外部 API),会影响主流程的性能。
解决办法:让监听器异步执行。Spring 提供了简单的异步支持:
- 在配置类上添加
@EnableAsync启用异步功能。 - 在监听器方法上添加
@Async注解。
示例:
@EnableAsync
@SpringBootApplication
public class DemoApplication { ... }
@Component
public class UserEventListener {
@Async
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
// 模拟耗时操作
Thread.sleep(2000);
System.out.println("发送欢迎邮件给:" + event.getEmail());
}
}
此时发布事件的方法会立即返回,监听器在独立的线程中执行。注意:异步需要配置线程池,否则会使用默认的 SimpleAsyncTaskExecutor(不推荐生产环境)。建议自定义线程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
}
这里加了配置后,就不需要在启动器类里面加了
五,事务事件监听器
5.1 什么是事务事件监听器?
事务事件监听器(@TransactionalEventListener)是 Spring 4.2 引入的一个特性,它是对普通事件监听器的扩展。它允许你将事件的监听绑定到当前事务的某个特定阶段,比如事务提交前、提交后、回滚后等。
简单来说,就是“当事务进行到某个时刻,再执行这个监听器”。
5.2 什么需要它?
来看一个典型场景:用户注册成功后,需要保存用户到数据库,然后发送一封欢迎邮件。
如果按照之前学的普通事件监听器,代码可能这样:
@Transactional
public void register(User user) {
userDao.save(user); // 数据库操作
eventPublisher.publishEvent(new UserRegisteredEvent(user));
}
监听器:
@EventListener
public void sendEmail(UserRegisteredEvent event) {
// 发送邮件
}
这里有个隐患:邮件发送是在事务提交之前执行的(默认同步)。如果邮件发送成功,但随后事务提交失败(例如数据库约束冲突),用户实际上没有注册成功,但邮件已经发出去了 —— 这就产生了数据不一致。
如果我们改成异步发送邮件,虽然主事务不受影响,但同样可能发生事务回滚后邮件依然发送的情况。
我们希望的是:只有当数据库事务真正提交成功后,才去发送邮件。如果事务回滚了,就不发邮件。这正是事务事件监听器要解决的问题。
5.3 @TransactionalEventListener 详解
5.3.1 核心注解属性
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
/**
* 指定监听器在事务的哪个阶段执行
*/
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
/**
* 如果没有事务时,是否仍然执行监听器(默认false,即没有事务时不执行)
*/
boolean fallbackExecution() default false;
/**
* 支持 SpEL 条件表达式,与 @EventListener 的 condition 类似
*/
String condition() default "";
}
5.3.2 事务阶段(TransactionPhase)
phase 可以取以下四个值:
| 阶段 | 说明 | 适用场景 |
|---|---|---|
TransactionPhase.BEFORE_COMMIT | 事务提交之前 | 需要在事务提交前做一些准备,比如二次校验,如果校验失败还能回滚事务 |
TransactionPhase.AFTER_COMMIT(默认) | 事务提交成功后 | 最常见,如发邮件、发消息、记录日志,要求事务成功后执行 |
TransactionPhase.AFTER_ROLLBACK | 事务回滚后 | 用于回滚后的补偿操作,如记录失败日志、告警 |
TransactionPhase.AFTER_COMPLETION | 事务完成后(无论提交还是回滚) | 需要清理资源等,无论成功失败都要执行 |
5.4 示例
还是用户注册的例子,我们用事务事件监听器改造:
5.4.1 定义事件
public class UserRegisteredEvent extends ApplicationEvent {
private final User user;
public UserRegisteredEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() { return user; }
}
5.4.2 发布事件(在事务方法内)
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private UserRepository userRepository;
@Transactional
public void register(User user) {
userRepository.save(user); // 数据库操作
// 发布事件,此时事务还未提交
publisher.publishEvent(new UserRegisteredEvent(this, user));
}
}
5.4.3 创建事务事件监听器
@Component
public class UserEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(UserRegisteredEvent event) {
User user = event.getUser();
System.out.println("事务已提交,发送邮件给:" + user.getEmail());
// 实际发邮件逻辑
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleAfterRollback(UserRegisteredEvent event) {
User user = event.getUser();
System.out.println("事务回滚了,记录失败日志:" + user.getUsername());
}
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleBeforeCommit(UserRegisteredEvent event) {
// 可以在提交前做一些额外校验
System.out.println("即将提交事务,检查数据...");
}
}
这样,无论事务提交还是回滚,监听器都会在正确的时机执行。
5.5 工作原理(简单理解)
Spring 的事务事件监听器是基于 ** 事务同步(Transaction Synchronization)** 机制实现的。大致流程:
- 当你在事务方法中发布事件时,Spring 不会立即调用监听器,而是将事件和监听器信息注册到当前事务的同步管理中。
- Spring 会在事务的不同阶段(提交前、提交后、回滚后)触发对应的同步回调。
- 在回调中,Spring 会取出对应阶段的事件,并执行监听器。
因此,事务事件监听器必须依赖于一个活跃的事务。如果发布事件时没有事务,默认监听器不会执行(除非设置 fallbackExecution = true)。
5.6 注意事项
-
事件必须在事务方法内发布
这是最关键的。如果你的
publishEvent调用不在事务方法中,那么事务事件监听器不会触发(除非设置fallbackExecution = true)。 -
事务管理器的配置
要使用事务事件,需要确保 Spring 配置了事务管理器(如
PlatformTransactionManager或ReactiveTransactionManager),并且启用了事务管理(@EnableTransactionManagement)。Spring Boot 通常会自动配置 -
同步 vs 异步
事务事件监听器默认也是同步执行的,即它们会在事务线程中执行(阻塞事务的后续流程,比如
AFTER_COMMIT在事务提交后、返回前执行)。如果监听器是耗时操作,建议结合@Async异步执行,但要注意:异步后,监听器执行不再受原事务控制,但因为是事务提交后才异步执行,所以数据一致性已经保证了。 -
回滚情况下的监听器
如果事务最终回滚了,那么
AFTER_COMMIT监听器不会执行,但AFTER_ROLLBACK和AFTER_COMPLETION会执行。这正好满足业务需求。 -
多个监听器的执行顺序
同一阶段内的多个监听器,默认顺序不确定。如果需要控制顺序,可以使用
@Order注解。 -
fallbackExecution 的作用
当事件发布不在事务中时,如果设置
fallbackExecution = true,监听器会立即执行(相当于普通@EventListener)。这可以应对某些无事务的情况。 -
事件发布后修改数据的问题
在 `BEFORE_COMMIT` 监听器中,你仍然可以修改数据库(因为事务还没提交),这些修改会包含在当前事务中。在 `AFTER_COMMIT` 监听器中,事务已提交,不能再进行数据库写操作(除非开启新事务),但可以读数据。
六,注意事项
- 事件类:建议继承
ApplicationEvent虽然不是强制,但这样更规范,也能利用一些框架特性(如事务事件)。 - 监听器异常:默认同步执行时,如果一个监听器抛出异常,会影响其他监听器(后续监听器可能不会执行)。异步执行时,异常不会影响主线程,但需要自行处理。
- 避免循环依赖:如果监听器又去发布事件,可能导致无限循环,要小心设计。
- 事件继承:监听器可以监听父类事件,所有子类事件也会被触发。例如监听
ApplicationEvent会收到所有事件。 - 条件过滤:
@EventListener支持 SpEL 表达式condition,可以根据事件属性决定是否执行。
示例:只对特定用户名的监听:
@EventListener(condition = "#event.username == 'admin'")
public void handleAdmin(UserRegisteredEvent event) { ... }