转载

事件驱动机制探索

一个service方法要执行非常多的操作,根据单一职责原则和开闭原则,想对一些方法进行解耦,避免在一个方法中存在太多逻辑。

要解决的问题有

  1. 逻辑解耦
  2. 易扩展
  3. 如何实现事务管理

Spring的ApplicationContext.publishEvent()方法

ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。

使用tips

  • 根据测试,默认是同步的
  • 可以使用@Order指定执行顺序
  • 对比使用@TransactionalEventListener
  • 要实现异步,Springboot项目中可以添加@EnableAsync和在对应监听方法上添加@Async注解

事务

  1. 使用@EventListener注解,在发布者上加@Transactional注解 默认是同步的,所以在发布者上声明放在同一个事务
/**
    * 订单生成流程:
    * 1. 生成订单
    * 2. 扣减库存: 异常时订单是否被回滚
    * 3. 发送邮件: 异常时扣减库存和订单是否被回滚
    **/
    @Override
    @Transactional
    public void saveOrder(Order order) {
        log.info("保存订单... 订单id={}", order.getOrderId());
        orderMapper.insert(order);
        Long orderId = order.getId();
        OrderDetail orderDetail = order.getOrderDetail();
        orderDetail.setOrderId(orderId);
        orderDetailMapper.insertSelective(orderDetail);
        applicationContext.publishEvent(new OrderEvent(order));
        log.info("订单保存成功");
    }
复制代码

定义事件处理顺序

public interface OrderConfirmOrder {

    /**
     * 默认
     **/
    int DEFAULT = 0;

    /**
     * 扣减库存
     **/
    int DEDUCT_STOCK = 100;

    /**
     * 发送邮件
     **/
    int SEND_EMAIL = 200;

}
复制代码

两个事件监听者,分别是扣减库存和发送邮件

@EventListener(OrderEvent.class)
@Order(OrderConfirmOrder.DEDUCT_STOCK)
public void deductStock(OrderEvent orderEvent) {
    Long orderId = orderEvent.getOrder().getId();
    OrderDetail detail = orderDetailMapper.findOneByOrderId(orderId);
    Long productId = detail.getProductId();
    Integer quantity = detail.getQuantity();
    Stock origin = stockMapper.findOneByProductId(productId);
    Stock stock = new Stock();
    stock.setId(origin.getId());
    // 扣库存
    stock.setAvailable(origin.getAvailable() - quantity);
    stockMapper.updateByPrimaryKeySelective(stock);
    log.info("扣减库存,原库存{}, 现在库存是{}", origin.getAvailable(), stock.getAvailable());
}
复制代码
@EventListener(OrderEvent.class)
    @Order(OrderConfirmOrder.SEND_EMAIL)
    public void sendEmail(OrderEvent orderEvent) {
        log.info("发送邮件. 订单id={}", orderEvent.getOrder().getOrderId());
    }
复制代码

程序正常执行,可以看到是同步且按顺序执行

事件驱动机制探索

库存正常扣减,现在我在库存扣减后抛出一个异常

@EventListener(OrderEvent.class)
@Order(OrderConfirmOrder.DEDUCT_STOCK)
public void deductStock(OrderEvent orderEvent) {
    Long orderId = orderEvent.getOrder().getId();
    OrderDetail detail = orderDetailMapper.findOneByOrderId(orderId);
    Long productId = detail.getProductId();
    Integer quantity = detail.getQuantity();
    Stock origin = stockMapper.findOneByProductId(productId);
    Stock stock = new Stock();
    stock.setId(origin.getId());
    // 扣库存
    stock.setAvailable(origin.getAvailable() - quantity);
    stockMapper.updateByPrimaryKeySelective(stock);
    log.info("扣减库存,原库存{}, 现在库存是{}", origin.getAvailable(), stock.getAvailable());
    throw new RuntimeException("扣减库存异常");
}
复制代码

结果order表, order_detail表和stock表都没有变化,说明在一个事务里,且都回滚了。

  1. 使用@TransactionalEventListener 同样,定义两个监听者
@TransactionalEventListener(classes = OrderEvent.class, phase = TransactionPhase.BEFORE_COMMIT)
@Order(OrderConfirmOrder.DEDUCT_STOCK)
public void deductStock(OrderEvent orderEvent) {
    Long orderId = orderEvent.getOrder().getId();
    OrderDetail detail = orderDetailMapper.findOneByOrderId(orderId);
    Long productId = detail.getProductId();
    Integer quantity = detail.getQuantity();
    Stock origin = stockMapper.findOneByProductId(productId);
    Stock stock = new Stock();
    stock.setId(origin.getId());
    // 扣库存
    stock.setAvailable(origin.getAvailable() - quantity);
    stockMapper.updateByPrimaryKeySelective(stock);
    log.info("扣减库存,原库存{}, 现在库存是{}", origin.getAvailable(), stock.getAvailable());
}
复制代码
@TransactionalEventListener(value = OrderEvent.class, phase = TransactionPhase.BEFORE_COMMIT)
@Order(OrderConfirmOrder.SEND_EMAIL)
public void sendEmail(OrderEvent orderEvent) {
    log.info("发送邮件. 订单id={}", orderEvent.getOrder().getOrderId());
}
复制代码

程序正常执行,并且库存扣减了。 下一步同理在库存扣减后抛出异常,测试结果是事务回滚了。

  • 特别注意 TransactionPhase有四个值, BEFORE_COMMIT , AFTER_COMMIT , AFTER_ROLLBACK , AFTER_COMPLETION 这里声明了执行阶段是 TransactionPhase.BEFORE_COMMIT ,如果是 AFTER_COMMIT 这里事务是不生效的,和使用@EventListener一样的结果,只会显示异常,并执行声明了 AFTER_COMPLETION 的监听者。
事件驱动机制探索
  1. Async

结论

@EventListener比较适合简单地应用在工程中,虽然是同步实现,但做到了逻辑的解耦,也可以保证事务和执行顺序,如果再增加流程的话,只需要增加监听者,不需要改动saveOrder()方法,遵守了软件设计原则。 @TransactionalEventListener对@EventListener进行了扩展,可以在事务不同的时期触发事件,应用场景待继续深入了解。

下一节会探讨一下Spring异步情况下监听器的使用,和Google Eventbus的使用与Spring的对比。

原文  https://juejin.im/post/5ef034bb6fb9a058747f41a0
正文到此结束
Loading...