一個service方法要執行很是多的操做,根據單一職責原則和開閉原則,想對一些方法進行解耦,避免在一個方法中存在太多邏輯。java
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("訂單保存成功");
}
複製代碼
定義事件處理順序app
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());
}
複製代碼
程序正常執行,能夠看到是同步且按順序執行ide
@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());
}
複製代碼
程序正常執行,而且庫存扣減了。 下一步同理在庫存扣減後拋出異常,測試結果是事務回滾了。spa
BEFORE_COMMIT
, AFTER_COMMIT
, AFTER_ROLLBACK
, AFTER_COMPLETION
這裏聲明瞭執行階段是TransactionPhase.BEFORE_COMMIT
,若是是AFTER_COMMIT
這裏事務是不生效的,和使用@EventListener同樣的結果,只會顯示異常,並執行聲明瞭AFTER_COMPLETION
的監聽者。@EventListener比較適合簡單地應用在工程中,雖然是同步實現,但作到了邏輯的解耦,也能夠保證事務和執行順序,若是再增長流程的話,只須要增長監聽者,不須要改動saveOrder()方法,遵照了軟件設計原則。 @TransactionalEventListener對@EventListener進行了擴展,能夠在事務不一樣的時期觸發事件,應用場景待繼續深刻了解。設計
下一節會探討一下Spring異步狀況下監聽器的使用,和Google Eventbus的使用與Spring的對比。code