事件机制
Laravel 的 Event + Listener 用
Event::dispatch()和php artisan make:event;Spring Boot 用 ApplicationEvent +@EventListener。连「发布-订阅」的思维都一模一样,只是 Java 里事件、监听器都是普通 Bean,注解声明即生效。
最小示例
1. 定义事件(Laravel 的 Event 类)
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)
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. 发布事件
@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;
}
}对照
| Laravel | Spring Boot |
|---|---|
php artisan make:event | 建一个普通类,无继承要求 |
php artisan make:listener | 建一个 @Component + @EventListener 方法 |
Event::dispatch($event) | applicationEventPublisher.publishEvent(event) |
| 按类名自动匹配监听器 | 按方法参数类型自动匹配 |
一个事件多个监听器
Laravel 里 Event::listen 注册多个 listener;Spring 里写多个 @EventListener 方法即可(或一个类里多个方法):
@Component
public class PostEventListeners {
@EventListener
public void sendMail(PostCreatedEvent event) { ... }
@EventListener
public void notifyFans(PostCreatedEvent event) { ... }
}异步监听
Laravel 用 ShouldQueue 把监听器放进队列;Spring 用 @Async:
@Component
public class PostEventListeners {
@Async // 异步执行,不等结果
@EventListener
public void sendMail(PostCreatedEvent event) { ... }
}前提:开启异步支持 + 一个线程池:
@Configuration
@EnableAsync
public class AsyncConfig { }TIP
@Async 和 Laravel 的 Queue::push 效果类似:发布者不等监听器执行完就返回。区别是 Spring 的 @Async 默认只做「线程池异步」,不保证持久化(进程崩了任务丢)。要可靠的任务队列,用 异步与队列。
监听器里拿不到事务的坑
publishEvent 默认在当前事务里同步执行监听器。如果在 @Transactional 方法里发事件,监听器里的数据库操作会在同一个事务里:
- 主操作失败回滚 → 监听器的操作也回滚(⚠️ 邮件可能发不出去或重复)
- 想要「提交成功后再发」:用
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Component
public class PostCreatedListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onPostCreated(PostCreatedEvent event) {
// 事务提交成功后才执行 —— 类似 Laravel 的 afterCommit 事件
}
}TIP
Laravel 事件可以在 dispatch() 后手动控制时机;Spring 用 @TransactionalEventListener 显式声明「在事务提交后」。这是 Java 事务模型下才有的精细控制。
事件用于解耦:一个真实场景
注册流程:用户注册 → 发欢迎邮件 → 通知运营 → 初始化默认数据。
没有事件(Service 里硬编码):
public User register(UserDto dto) {
User user = repository.save(...);
mailer.sendWelcome(user); // 服务里直接调用
notifyOps.send(user); // 新增逻辑就要改这里
initDefaults.init(user);
return user;
}每次加一个「注册后的动作」都要改 register() 方法,服务越来越胖、依赖越来越乱。
有事件:
public User register(UserDto dto) {
User user = repository.save(...);
publisher.publishEvent(new UserRegisteredEvent(user)); // 只发一个事件
return user;
}@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 里同样成立。判别标准也一样:不知道谁需要这个数据时,用事件;能明确调用方时,直接注入调用。
常见坑
- 在事务内同步发外部副作用:发邮件、调第三方 API 放在
AFTER_COMMIT监听器里,或转异步 - 忘记
@Component:监听器不加注解,永远不会被扫描到 - 同包命名冲突:两个同名的
XxxEvent类(不同模块)要小心@EventListener按参数类型精确匹配 - 同步监听器阻塞主流程:默认同步执行,耗时监听器会拖慢请求,必要时
@Async