楼层: 首页/ 软件技术/ Spring 核心框架/ Spring 事件机制:对象之间别直接喊
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。默认它只在事务成功提交后才处理事件,事务回滚了就不发邮件。解析:这是"数据还没存进去就通知用户"的经典坑,注册成功邮件尤其要注意。