Spring事件,ApplicationEvent在業務中的應用

烏托拉賽文發表於2021-10-17

前言

關於事件驅動模型,百度百科在有明確的解釋。在JDKUtil包裡抽象了事件驅動,有興趣的朋友可以自行去看下相關類的定義。Spring事件模型ApplicationEvent是基於JDK裡的事件模型,廢話不多說,直接看Spring是如何定義事件模型,以及在具體業務場景中的應用。

事件

事件就是事件,滑鼠點選一下算一個事件,某個按鈕被點選了一下算一個點選事件,那麼我訂單支付了可以認為支付也算一個件事!觸發了某個事件...... 等等。

抽象類ApplicationEvent承載著我們要傳播的事件或者訊息,白話就是說可以把某個物件用ApplicationEvent這個物件來裡的Source來引用。

監聽者

上面我們定義了事件,那麼事件產生的一系列的效應或者是變動,那麼都由這些監聽者們去實現。點選下滑鼠(事件),那麼我記錄下日誌,你彈出個提示框。支付某個訂單(事件),我記錄下記錄,他傳送個支付通知...... 等等。

泛型介面ApplicationListener規定了泛型E的上邊界為ApplicationEvent,意思很明確,就是給我們自定義事件用的。Spring最大的優點我認為是留給使用者發揮的空間很大,就像神祕的海洋一樣,它一直有你探索不完的祕密,每一次你去了解它,它都能給你帶來新的事物和理解。

實戰

文章SpringPlugin-Core在業務中的應用中,我們用SpringPlugin外掛的方式去實現了訂單的不同操作!而在某個操作裡面,我們可能又要傳送操作事件的通知,比如:訂單支付了後,要通知印表機列印小票、微信公眾號提醒支付資訊等等。那麼我們來實際的操作下。

定義事件源

public class OrderPayedEvent extends ApplicationEvent {

    /**
     * 訊息體,這裡就設定為當前訂單物件
     */
    private final Order order;


    public OrderPayedEvent(Object source) {
        super(source);
        this.order = (Order) source;
    }
    
    public Order getOrder() {
        return order;
    }
}

實現ApplicationEvent, 我這裡Source實際傳遞就是Order物件,當然你也可以定義其他的多引數建構函式!

定義監聽者

定義監聽者的方式,Spring提供了兩種,一種是介面方式,一種是註解方式。

介面方式

@Component
@Order(1)
public class OrderPayedPrinterListener implements ApplicationListener<OrderPayedEvent> {

    @Override
    public void onApplicationEvent(OrderPayedEvent event) {
        System.out.printf("【執行緒 - %s 】訂單成功成功:第一步,列印小票%n", Thread.currentThread().getName());
    }
}


@Component
@Order(2)
public class OrderPayedSendMessageListener implements ApplicationListener<OrderPayedEvent> {

    @Override
    public void onApplicationEvent(OrderPayedEvent event) {
        System.out.printf("【執行緒 - %s 】訂單成功成功:第二步,傳送通知商品中心新增庫存%n", Thread.currentThread().getName());
    }
}

這裡我定義了兩個監聽者,實現泛型介面ApplicationListener型別為我們剛定義的OrderPayedEvent這裡加上Order註解,是因為我們有多個監聽者,有此業務場景中可能會有順序的要求!

註解方式

@Component
public class OrderPayListener {

    @EventListener(classes = {OrderPayedEvent.class})
    public void sendTips(OrderPayedEvent event) {
        System.out.printf("【執行緒 - %s 】訂單成功成功:傳送使用者訂單支付訊息%n", Thread.currentThread().getName());
    }

    @EventListener(classes = {OrderPayedEvent.class})
    public void reward(OrderPayedEvent event) {
        System.out.printf("【執行緒 - %s 】訂單成功成功:獎勵業務%n", Thread.currentThread().getName());
    }
}

兩種方式,各有千秋,不同業務場景選擇不同實現方式即可。但註解方式是不會有排序功能的,如果你有業務有需要排序,那麼建議換成介面方式

釋出件事

萬事具備,只欠東風。那麼只要合適的位置釋出事件即可,那麼在上回文章中的支付成功程式碼加上事件即可

@Component
public class PayOperator implements OrderOperatorPlugin {
	
  	//這裡注入 應用上下文,可以注入 applicationEventPublisher
    @Resource
    ApplicationContext context;
	
  	// @Resource
    // ApplicationEventPublisher applicationEventPublisher;
  
    @Override
    public Optional<?> apply(OrderOperatorDTO operator) {
        //支付操作
        //doPay()

        //傳送事件
        context.publishEvent(new OrderPayedEvent(new Order()));
        return Optional.of("支付成功");
    }

    @Override
    public boolean supports(OrderOperatorDTO operatorDTO) {
        return operatorDTO.getOperatorType() == OrderOperatorType.PAY;
    }
}

列印如下:

【執行緒 - main 】訂單成功成功:第一步,列印小票
【執行緒 - main 】訂單成功成功:第二步,傳送通知商品中心新增庫存
【執行緒 - main 】訂單成功成功:第四步,獎勵業務
【執行緒 - main 】訂單成功成功:第三步,傳送使用者訂單支付訊息

那麼,ApplicationEvent對非同步支援是怎麼樣的呢?

只要在啟動類上加上@EnableAsync,在方法體加上@Async

再列印如下:

【執行緒 - task-1 】訂單成功成功:第一步,列印小票
【執行緒 - task-2 】訂單成功成功:第二步,傳送通知商品中心新增庫存
【執行緒 - task-4 】訂單成功成功:第四步,獎勵業務
【執行緒 - task-3 】訂單成功成功:第三步,傳送使用者訂單支付訊息

總結

不管是EventObject,還是Observable模型,都是用來解耦程式碼。高內聚,低耦合的設計思想一至到現在都沒有被突破過,也是我們在日常工作過程中時刻要提醒自己的編碼思想。而我們更要利用好這些前人留下的精髓,應用到我們實際的業務場景中去。

[程式碼在GitHub](

相關文章