一个service方法要执行非常多的操作,根据单一职责原则和开闭原则,想对一些方法进行解耦,避免在一个方法中存在太多逻辑。
ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。
/** * 订单生成流程: * 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表都没有变化,说明在一个事务里,且都回滚了。
@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()); } 复制代码
程序正常执行,并且库存扣减了。 下一步同理在库存扣减后抛出异常,测试结果是事务回滚了。
BEFORE_COMMIT
, AFTER_COMMIT
, AFTER_ROLLBACK
, AFTER_COMPLETION
这里声明了执行阶段是 TransactionPhase.BEFORE_COMMIT
,如果是 AFTER_COMMIT
这里事务是不生效的,和使用@EventListener一样的结果,只会显示异常,并执行声明了 AFTER_COMPLETION
的监听者。 @EventListener比较适合简单地应用在工程中,虽然是同步实现,但做到了逻辑的解耦,也可以保证事务和执行顺序,如果再增加流程的话,只需要增加监听者,不需要改动saveOrder()方法,遵守了软件设计原则。 @TransactionalEventListener对@EventListener进行了扩展,可以在事务不同的时期触发事件,应用场景待继续深入了解。
下一节会探讨一下Spring异步情况下监听器的使用,和Google Eventbus的使用与Spring的对比。