前言
關於事件驅動模型,百度百科在有明確的解釋。在
JDK
的Util
包裡抽象了事件驅動,有興趣的朋友可以自行去看下相關類的定義。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](