Skip to content

事件与监听


@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 只负责发布事件,不需要知道谁会处理。监听器只负责处理事件,不需要知道谁发布的。这个是观察者模式在两边框架中的共同设计。

面向 PHP 开发者的 Spring Boot 文档