7
Spring 事件机制:对象之间别直接喊
Application Events
用户注册成功后,你要发邮件、发短信、写积分、加新人引导……如果 UserService 里依次调用 EmailService、SmsService、PointService、GuideService,就是四个紧耦合的依赖。事件机制的思路是:UserService 只负责"用户注册成功了"这个事件广播出去,谁感兴趣谁来听,发布者根本不知道有哪些监听者。
三步走:定义事件 → 发布事件 → 监听事件
// 第一步:定义一个事件(继承 ApplicationEvent 或直接用任意 POJO)
public class UserRegisteredEvent {
private final long userId;
public UserRegisteredEvent(long userId) {
this.userId = userId;
}
public long getUserId() { return userId; }
}
// 第二步:在业务代码里发布事件
@Service
public class UserService {
private final ApplicationEventPublisher publisher;
public UserService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(String name) {
// ... 保存用户 ...
long userId = 1001;
// 广播:有人注册了!具体谁关心,我不关心
publisher.publishEvent(new UserRegisteredEvent(userId));
}
}
// 第三步:监听者各自干活
@Component
public class EmailListener {
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
System.out.println(">> 给用户#" + event.getUserId() + " 发欢迎邮件");
}
}
@Component
public class PointListener {
// 加 @Async 让监听异步执行,不阻塞主流程
@Async
@EventListener
public void givePoints(UserRegisteredEvent event) {
System.out.println(">> 给用户#" + event.getUserId() + " 送 100 积分");
}
}
UserService.register("张三") 输出:
>> 给用户#1001 发欢迎邮件
>> 给用户#1001 送 100 积分
事务绑定事件:@TransactionalEventListener
// 业务场景:用户注册成功后要发邮件。
// 问题:如果用户事务回滚了,邮件已经发出去了,那就尴尬了。
// 解法:@TransactionalEventListener 只在事务"成功提交后"才触发。
@Component
public class TransactionalEmailListener {
// phase = AFTER_COMMIT 表示事务提交后才执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void onUserRegistered(UserRegisteredEvent event) {
// 走到这里说明用户已经入库成功,放心发邮件
System.out.println(">> 事务已提交,发邮件给用户#" + event.getUserId());
}
}
| phase | 触发时机 |
|---|---|
BEFORE_COMMIT | 事务提交前。 |
AFTER_COMMIT(默认) | 事务成功提交后。最常用——主业务成了再做后续动作。 |
AFTER_ROLLBACK | 事务回滚后。 |
AFTER_COMPLETION | 事务结束(不管提交还是回滚)。 |
本章面试题 · 第 7 章(事件机制)
1.(理解题)Spring 事件机制解决什么问题?为什么不用直接调用?
查看答案
答案:解耦发布者和监听者。注册成功后要发邮件、发短信、加积分——主业务里直接调三个 Service 就耦合死了。发一个 UserRegisteredEvent,谁感兴趣谁监听,主业务不用知道有多少个监听器。解析:新增一个动作(比如发推送)不用改主业务代码,加个监听器就行——开闭原则。
2.(排错题)用事件发邮件,结果事务回滚了,邮件却已经发出去了。怎么避免?
查看答案
答案:用 @TransactionalEventListener 而不是 @EventListener。默认它只在事务成功提交后才处理事件,事务回滚了就不发邮件。解析:这是"数据还没存进去就通知用户"的经典坑,注册成功邮件尤其要注意。