一个service方法要执行非常多的操作,根据单一职责原则和开闭原则,想对一些方法进行解耦,避免在一个方法中存在太多逻辑。
要解决的问题有
- 逻辑解耦
- 易扩展
- 如何实现事务管理
Spring的ApplicationContext.publishEvent()方法
ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。
使用tips
- 根据测试,默认是同步的
- 可以使用@Order指定执行顺序
- 对比使用@TransactionalEventListener
- 要实现异步,Springboot项目中可以添加@EnableAsync和在对应监听方法上添加@Async注解
事务
- 使用@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表都没有变化,说明在一个事务里,且都回滚了。
- 使用@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
的监听者。

- Async
结论
@EventListener比较适合简单地应用在工程中,虽然是同步实现,但做到了逻辑的解耦,也可以保证事务和执行顺序,如果再增加流程的话,只需要增加监听者,不需要改动saveOrder()方法,遵守了软件设计原则。 @TransactionalEventListener对@EventListener进行了扩展,可以在事务不同的时期触发事件,应用场景待继续深入了解。
原文
https://juejin.im/post/5ef034bb6fb9a058747f41a0
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 事件驱动机制探索