起因
一个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进行了扩展,可以在事务不同的时期触发事件,应用场景待继续深入了解。
下一节会探讨一下Spring异步情况下监听器的使用,和Google Eventbus的使用与Spring的对比。