原创

TransactionalEventListener使用场景与源码分析

温馨提示:
本文最后更新于 2023年11月16日,已超过 433 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

背景

日常开发中,我们经常会遇到需要前置数据入库后,再执行后续业务逻辑的需求;比如:会员注册成功发送短信/邮件通知、充值完成后赠送积分、预定完成后发送消息通知等等;会员注册、充值、预定等都是我们的主线业务,而消息通知、赠送积分等属于附加业务;如果我们把主线业务和附加业务放在同一个事务内部,会不会出现意想不到的问题呢?

示例代码:

@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;

源码

然后通过反射找到对应的监听器并调用执行监听器。

源码

最后如果监听器有返回值则处理返回结果

源码

正文到此结束
本文目录