事件与监听
@EventListener(类比 Event::listen)
java
// 1. 定义事件(POJO 类,不需要继承)
public class UserRegisteredEvent {
private final String email;
private final LocalDateTime registeredAt;
public UserRegisteredEvent(String email) {
this.email = email;
this.registeredAt = LocalDateTime.now();
}
// getter
}
// 2. 触发事件
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher; // 类比 Event::dispatch()
public void register(String email) {
// 保存用户
userMapper.insert(user);
// 触发事件(类比 Event::dispatch(new UserRegisteredEvent($email)))
eventPublisher.publishEvent(new UserRegisteredEvent(email));
}
}
// 3. 监听事件
@Component
public class UserEventListener {
@EventListener // 类比 Event::listen(UserRegisteredEvent::class, function($event) { ... })
public void sendWelcomeEmail(UserRegisteredEvent event) {
// 发送欢迎邮件
log.info("发送欢迎邮件到: {}", event.getEmail());
}
@EventListener
public void logRegistration(UserRegisteredEvent event) {
log.info("用户注册: {}", event.getEmail());
}
}同步 vs 异步
默认情况下,事件监听器是同步执行的——事件发布后,监听器在当前线程逐个执行,全部执行完才返回。
java
// 以下代码:
eventPublisher.publishEvent(new UserRegisteredEvent(email));
log.info("注册完成");
// 执行顺序:
// 1. publishEvent → 执行所有监听器 → 2. log.info异步监听器
java
@Component
public class UserEventListener {
@Async // 异步执行
@EventListener
public void sendWelcomeEmail(UserRegisteredEvent event) {
Thread.sleep(3000); // 模拟耗时操作
log.info("邮件发送完成");
}
}启用异步
java
@SpringBootApplication
@EnableAsync // 开启异步执行
public class DemoApplication { ... }@TransactionalEventListener
java
@Component
public class UserEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendWelcomeEmail(UserRegisteredEvent event) {
// 只在事务提交后才执行
// 如果前面的数据库操作回滚了,监听器不会执行
}
}对应 PHP 中
Event::listen()结合DB::afterCommit()的场景——确保事务提交后再执行副作用操作。
| phase | 执行时机 |
|---|---|
AFTER_COMMIT(默认) | 事务提交后执行 |
AFTER_ROLLBACK | 事务回滚后执行 |
AFTER_COMPLETION | 事务完成后(提交或回滚) |
BEFORE_COMMIT | 事务提交前执行 |
Event 类设计
java
// 不变的事件类(推荐)
public class OrderPaidEvent {
private final Long orderId; // final:事件数据不可变
private final BigDecimal amount;
public OrderPaidEvent(Long orderId, BigDecimal amount) {
this.orderId = orderId;
this.amount = amount;
}
// 只提供 getter,不提供 setter
}
// 多个事件监听器可以接收同一个事件
@Component
public class OrderEventListener {
@EventListener
public void updateInventory(OrderPaidEvent event) {
// 扣减库存
}
@EventListener
public void sendNotification(OrderPaidEvent event) {
// 发送通知
}
@EventListener
@Async
public void generateInvoice(OrderPaidEvent event) {
// 生成发票(异步,不阻塞主流程)
}
}⚠️ 常见坑
1. 事件默认同步,性能敏感时要用 @Async
如果一个事件有三个监听器,每个耗时 1 秒,客户端需要等 3 秒才能收到响应。需要异步的场景明确加
@Async。
2. @Async 需要 @EnableAsync
和
@EnableCaching、@EnableScheduling一样,功能开关默认关闭。
3. Spring 事件和 Laravel 事件的哲学差异
- Laravel:事件和监听器配置在
EventServiceProvider中,显式注册- Spring:通过
@EventListener注解自动发现,不需要注册Spring 的方式更简洁,缺点是事件和监听器之间的关联不直观——你不知道某个事件有哪些监听器(IDE 的 Find Usages 可以查看)。
4. 事件类不需要实现接口
PHP 中事件通常要实现
ShouldBroadcast等接口,Spring 的事件可以是任何 POJO,没有任何约束。
5. 事件发布者不需要知道监听者
ApplicationEventPublisher只负责发布事件,不需要知道谁会处理。监听器只负责处理事件,不需要知道谁发布的。这个是观察者模式在两边框架中的共同设计。