TransactionalEventListener使用场景与源码分析
背景
日常开发中,我们经常会遇到需要前置数据入库后,再执行后续业务逻辑的需求;比如:会员注册成功发送短信/邮件通知、充值完成后赠送积分、预定完成后发送消息通知等等;会员注册、充值、预定等都是我们的主线业务,而消息通知、赠送积分等属于附加业务;如果我们把主线业务和附加业务放在同一个事务内部,会不会出现意想不到的问题呢?
示例代码:
@Autowired
private UserService userService;
@Autowired
private SmsService smsService;
@Transactional
public void register(User user){
// 注册
userService.register(user);
try {
// 发送注册成功通知消息,防止因通知失败导致注册失败
smsService.sendSms(user.getPhone());
} catch (Exception e){
log.error("用户注册成功但通知失败,异常信息: {}", e.getMessage(), e);
}
}
上面的代码非常简单,业务逻辑也很清晰,只做注册和通知两件事,正常来说用户注册完成然后发送短信/邮件告知用户没有任何问题。
但实际生产环境中,总会出现一些非正常情况的BUG;比如:
因为某些未知问题导致通知成功后事务提交失败,这时候用户收到了注册成功的通知但实际并没有此用户信息;
这是不可容忍的情况。根据墨菲定律,可能发生的事在生产环境中一定会发生。所以我们需要对上面的代码进行改造。
我们希望在用户注册完成并落库后,通过短信/邮件通知用户;
@Autowired
private ApplicationEventPublisher eventPublisher;
public void register(User user){
// 注册
userService.register(user);
eventPublisher.publishEvent(new TestEvent(user));
}
public class RegisterEvent extends ApplicationEvent {
private User user;
public RegisterEvent(User user) {
super(user);
this.user = user;
}
}
@Component
public class RegisterEventListener {
private Logger log = LoggerFactory.getLogger(RegisterEventListener.class);
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true, classes = RegisterEvent.class)
public void sendSms(RegisterEvent event){
log.info("用户注册完成发送短信通知: {}", event);
}
}
输出:
用户注册完成: User{id=1, name='测试', phone='18888888888'}
用户注册完成发送短信通知: RegisterEvent[source=User{id=1, name='测试', phone='18888888888'}]
TransactionalEventListener
TransactionalEventListener是对EventListener的增强,被注解的方法可以在事务的不同阶段去触发执行,如果事件未在激活的事务中发布,除非显式设置了 fallbackExecution() 标志为true,否则该事件将被丢弃;如果事务正在运行,则根据其 TransactionPhase 处理该事件。
我们先看看TransactionPhase有以下四种:
- AFTER_COMMIT - 默认设置,在事务提交后执行
- AFTER_ROLLBACK - 在事务回滚后执行
- AFTER_COMPLETION - 在事务完成后执行(不管是否成功)
- BEFORE_COMMIT - 在事务提交前执行
当我们执行eventPublisher.publishEvent(new TestEvent(user))将给定事件发布到所有监听器,Spring会返回与给定事件类型匹配的ApplicationListeners集合,并不匹配的监听器会被提前排除。
当找到指定监听器后便开始执行调用逻辑,检查条件是否匹配并处理指定的ApplicationEvent;
然后通过反射找到对应的监听器并调用执行监听器。
最后如果监听器有返回值则处理返回结果
- 本文标签: java spring
- 本文链接: https://www.58cto.cn/article/21
- 版权声明: 本文由程序言原创发布, 非商业性可自由转载、引用,但需署名作者且注明文章出处:程序言 》 TransactionalEventListener使用场景与源码分析 - https://www.58cto.cn/article/21