一个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的对比。