文档站点
Skip to content

事件机制 ​

Laravel 的 Event + Listener 用 Event::dispatch() 和 php artisan make:event;Spring Boot 用 ApplicationEvent + @EventListener。连「发布-订阅」的思维都一模一样,只是 Java 里事件、监听器都是普通 Bean,注解声明即生效。

最小示例 ​

1. 定义事件(Laravel 的 Event 类) ​

java
package com.example.blog.post.event;

import lombok.Getter;

@Getter
public class PostCreatedEvent {
    private final Long postId;
    private final String title;

    public PostCreatedEvent(Long postId, String title) {
        this.postId = postId;
        this.title = title;
    }
}

2. 定义监听器(Laravel 的 Listener) ​

java
package com.example.blog.post.event;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class PostCreatedListener {

    @EventListener
    public void onPostCreated(PostCreatedEvent event) {
        System.out.println("发邮件通知: 新帖 " + event.getTitle());
    }
}

3. 发布事件 ​

java
@Service
public class PostService {
    private final ApplicationEventPublisher publisher;

    public PostService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Transactional
    public Post create(PostDto dto) {
        Post post = repository.save(dto.toEntity());
        publisher.publishEvent(new PostCreatedEvent(post.getId(), post.getTitle()));
        return post;
    }
}

对照

LaravelSpring Boot
php artisan make:event建一个普通类,无继承要求
php artisan make:listener建一个 @Component + @EventListener 方法
Event::dispatch($event)applicationEventPublisher.publishEvent(event)
按类名自动匹配监听器按方法参数类型自动匹配

一个事件多个监听器 ​

Laravel 里 Event::listen 注册多个 listener;Spring 里写多个 @EventListener 方法即可(或一个类里多个方法):

java
@Component
public class PostEventListeners {

    @EventListener
    public void sendMail(PostCreatedEvent event) { ... }

    @EventListener
    public void notifyFans(PostCreatedEvent event) { ... }
}

异步监听 ​

Laravel 用 ShouldQueue 把监听器放进队列;Spring 用 @Async:

java
@Component
public class PostEventListeners {

    @Async                       // 异步执行,不等结果
    @EventListener
    public void sendMail(PostCreatedEvent event) { ... }
}

前提:开启异步支持 + 一个线程池:

java
@Configuration
@EnableAsync
public class AsyncConfig { }

TIP

@Async 和 Laravel 的 Queue::push 效果类似:发布者不等监听器执行完就返回。区别是 Spring 的 @Async 默认只做「线程池异步」,不保证持久化(进程崩了任务丢)。要可靠的任务队列,用 异步与队列。

监听器里拿不到事务的坑 ​

publishEvent 默认在当前事务里同步执行监听器。如果在 @Transactional 方法里发事件,监听器里的数据库操作会在同一个事务里:

  • 主操作失败回滚 → 监听器的操作也回滚(⚠️ 邮件可能发不出去或重复)
  • 想要「提交成功后再发」:用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
java
@Component
public class PostCreatedListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onPostCreated(PostCreatedEvent event) {
        // 事务提交成功后才执行 —— 类似 Laravel 的 afterCommit 事件
    }
}

TIP

Laravel 事件可以在 dispatch() 后手动控制时机;Spring 用 @TransactionalEventListener 显式声明「在事务提交后」。这是 Java 事务模型下才有的精细控制。

事件用于解耦:一个真实场景 ​

注册流程:用户注册 → 发欢迎邮件 → 通知运营 → 初始化默认数据。

没有事件(Service 里硬编码):

java
public User register(UserDto dto) {
    User user = repository.save(...);
    mailer.sendWelcome(user);      // 服务里直接调用
    notifyOps.send(user);          // 新增逻辑就要改这里
    initDefaults.init(user);
    return user;
}

每次加一个「注册后的动作」都要改 register() 方法,服务越来越胖、依赖越来越乱。

有事件:

java
public User register(UserDto dto) {
    User user = repository.save(...);
    publisher.publishEvent(new UserRegisteredEvent(user));   // 只发一个事件
    return user;
}
java
@Component
public class WelcomeMailListener {
    @EventListener
    public void on(UserRegisteredEvent e) { mailer.sendWelcome(e.getUser()); }
}

@Component
public class NotifyOpsListener {
    @EventListener
    public void on(UserRegisteredEvent e) { notifyOps.send(e.getUser()); }
}

以后要加「注册后送积分」,只新增一个监听器,register() 一行不改。这就是发布-订阅的解耦价值。

和 Laravel 的结论完全一致

Laravel 文档那句「事件让你把业务逻辑拆成独立单元、各自关注一件事」,在 Spring 里同样成立。判别标准也一样:不知道谁需要这个数据时,用事件;能明确调用方时,直接注入调用。

常见坑 ​

  1. 在事务内同步发外部副作用:发邮件、调第三方 API 放在 AFTER_COMMIT 监听器里,或转异步
  2. 忘记 @Component:监听器不加注解,永远不会被扫描到
  3. 同包命名冲突:两个同名的 XxxEvent 类(不同模块)要小心 @EventListener 按参数类型精确匹配
  4. 同步监听器阻塞主流程:默认同步执行,耗时监听器会拖慢请求,必要时 @Async

面向 PHP / Laravel 开发者的 Spring Boot 中文文档