事件驅動機制探索

原由

一個service方法要執行很是多的操做,根據單一職責原則和開閉原則,想對一些方法進行解耦,避免在一個方法中存在太多邏輯。java

要解決的問題有

  1. 邏輯解耦
  2. 易擴展
  3. 如何實現事務管理

Spring的ApplicationContext.publishEvent()方法

ApplicationContext 經過 ApplicationEvent 類和 ApplicationListener 接口進行事件處理。 若是將實現 ApplicationListener 接口的 bean 注入到上下文中,則每次使用 ApplicationContext 發佈 ApplicationEvent 時,都會通知該 bean。本質上,這是標準的觀察者設計模式。設計模式

使用tips

  • 根據測試,默認是同步的
  • 可使用@Order指定執行順序
  • 對比使用@TransactionalEventListener
  • 要實現異步,Springboot項目中能夠添加@EnableAsync和在對應監聽方法上添加@Async註解

事務

  1. 使用@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("訂單保存成功");
    }
複製代碼

定義事件處理順序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表都沒有變化,說明在一個事務裏,且都回滾了。測試

  1. 使用@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());
}
複製代碼

程序正常執行,而且庫存扣減了。 下一步同理在庫存扣減後拋出異常,測試結果是事務回滾了。spa

  • 特別注意 TransactionPhase有四個值,BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION 這裏聲明瞭執行階段是TransactionPhase.BEFORE_COMMIT,若是是AFTER_COMMIT這裏事務是不生效的,和使用@EventListener同樣的結果,只會顯示異常,並執行聲明瞭AFTER_COMPLETION的監聽者。

  1. Async

結論

@EventListener比較適合簡單地應用在工程中,雖然是同步實現,但作到了邏輯的解耦,也能夠保證事務和執行順序,若是再增長流程的話,只須要增長監聽者,不須要改動saveOrder()方法,遵照了軟件設計原則。 @TransactionalEventListener對@EventListener進行了擴展,能夠在事務不一樣的時期觸發事件,應用場景待繼續深刻了解。設計

下一節會探討一下Spring異步狀況下監聽器的使用,和Google Eventbus的使用與Spring的對比。code

相關文章
相關標籤/搜索