參考:SpringBoot使用ApplicationEvent&Listener完成業(yè)務解耦
參考:Spring Boot 解耦之事件驅(qū)動
一、前言
1壹置、1 使用場景
日常開發(fā)中竞思,常見的比如用戶注冊賬號操作,當用戶注冊完畢之后蒸绩,可能還需要處理以下一些事情:
- 發(fā)送確認郵件
- 贈送用戶積分衙四、成長值
- 贈送優(yōu)惠券
問題:
- 假如以上所有的操作全部都耦合在一個service業(yè)務處理代碼中铃肯,后續(xù)操作一直沒有完成患亿,那么用戶是不是要長時間等待
- 如果郵件服務器掛了,注冊還能成功嗎
- 后期維護起來相關(guān)代碼也會非常麻煩押逼,甚至會出現(xiàn)一些漏洞等
從上述例子可以看出步藕,當用戶注冊完畢之后,發(fā)布一個命令給第三方的觀察者挑格,觀察者接收到相關(guān)命令之后咙冗,就可以來處理之后的相關(guān)事件,那么程序就可以解耦各個環(huán)節(jié)的依賴關(guān)系漂彤,這就是事件驅(qū)動模型雾消,內(nèi)部實現(xiàn)原理是觀察者設(shè)計模式。
1挫望、2 事件驅(qū)動定義
事件驅(qū)動模型也就是我們常說的觀察者立润,或者發(fā)布-訂閱模型;理解它的幾個關(guān)鍵點:
- 首先是一種對象間的一對多的關(guān)系媳板;最簡單的如交通信號燈桑腮,信號燈是目標(一方),行人注視著信號燈(多方)蛉幸;
- 當目標發(fā)送改變(發(fā)布)破讨,觀察者(訂閱者)就可以接收到改變丛晦;
- 觀察者如何處理(如行人如何走,是快走/慢走/不走提陶,目標不會管的)烫沙,目標無需干涉;所以就松散耦合了它們之間的關(guān)系隙笆。
spring主要是通過ApplicationEvent
以及Listener
為我們提供事件監(jiān)聽斧吐、訂閱等相關(guān)事件處理。
二仲器、項目中應用
2煤率、1 搭建springboot項目
略。
2乏冀、2 定義事件
/**
* 自定義訂單監(jiān)聽事件蝶糯,繼承了ApplicationEvent,并重載構(gòu)造函數(shù)
* <p>
* 構(gòu)造函數(shù)的參數(shù)可以任意指定辆沦,其中source參數(shù)指的是發(fā)生事件的對象昼捍,而第二個參數(shù)是我們自定義的注冊事件對象污桦,該對象可以在監(jiān)聽內(nèi)被獲取腰素。
*/
@Getter
public class OrderCancelEvent extends ApplicationEvent {
// 注入訂單業(yè)務對象
private OmsOrder omsOrder;
public OrderCancelEvent(Object source, OmsOrder omsOrder) {
super(source);
this.omsOrder = omsOrder;
}
}
事件是事件驅(qū)動的核心,上述OrderCancelEvent
就是自定義的一個事件迷殿,繼承了ApplicationEvent
蔚晨,并重寫了其構(gòu)造函數(shù)乍钻,第一個參數(shù)一般我們在發(fā)布事件時使用的是this
關(guān)鍵字代替本類對象,而第二個參數(shù)則根據(jù)具體業(yè)務具體定義铭腕,主要就是為了使監(jiān)聽器可以監(jiān)聽到相關(guān)事件银择。
2、3 創(chuàng)建事件監(jiān)聽器
事件監(jiān)聽器的創(chuàng)建方式有好多種累舷,eg:
@EventListener
注解浩考、實現(xiàn)ApplicationListener
泛型接口、實現(xiàn)SmartApplicationListener
接口等被盈,我們下面來講解下這三種方式分別如何實現(xiàn)析孽。
@EventListener
使用該注解是最簡單的一種方式,只需在方法上加上此注解即可只怎。
/**
* 用途:消息事件監(jiān)聽器
* 作者: jingwenhao
* 時間: 2019/7/26 15:29
*/
@Component
@Slf4j
public class MsgSendListener {
@Autowired
private IImsNotificationService notificationService;
/**
* 支付成功之后袜瞬,異步發(fā)送消息事件
*
* @param event 消息事件
*/
@EventListener
@Async
public void sendMsgAfterPaySucc(MsgOrderPayEvent event) {
try {
log.debug("——發(fā)送消息事件開始執(zhí)行——");
Thread.sleep(3000);// 休息3秒
// 獲取事件實際對象
Map<String, Object> eventMap = event.getEventMap();
// 具體的業(yè)務邏輯方法
notificationService.sendMsgAfterPaySucc(eventMap);
log.debug("——發(fā)送消息事件結(jié)束——");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 超時訂單關(guān)閉,異步發(fā)送消息事件
*
* @param event 消息事件
*/
@EventListener
@Async
public void sendMsgAfterAutoCancel(MsgOrderCancelEvent event) {
try {
log.debug("——發(fā)送消息事件開始執(zhí)行——");
Thread.sleep(3000);// 休息3秒
Map<String, Object> eventMap = event.getEventMap();
notificationService.sendMsgAfterAutoCancel(eventMap);
log.debug("——發(fā)送消息事件結(jié)束——");
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 實現(xiàn)ApplicationListener泛型接口
@Component
public class RegisterListener implements ApplicationListener<UserRegisterEvent>
{
/**
* 實現(xiàn)監(jiān)聽
* @param userRegisterEvent
*/
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
//獲取注冊用戶對象
UserBean user = userRegisterEvent.getUser();
//../省略邏輯
//輸出注冊用戶信息
System.out.println("注冊信息尝盼,用戶名:"+user.getName()+"吞滞,密碼:"+user.getPassword());
}
}
這里直接復制了博客上的寫法,這種寫法主要是實現(xiàn)了ApplicationListener接口,并將事先定義好的事件作為泛型對象傳遞了過去裁赠,UserRegisterEvent事件發(fā)布時監(jiān)聽程序會自動調(diào)用onApplicationEvent方法并且將UserRegisterEvent對象作為參數(shù)傳遞殿漠。
- 實現(xiàn)SmartApplicationListener
/**
* 訂單超時自動關(guān)閉監(jiān)聽任務
*/
@Component
@Slf4j
public class OrderAutoCloseListener implements SmartApplicationListener {
@Autowired
private IOmsOrderService orderService;//注入訂單業(yè)務接口
/**
* 該方法返回true&supportsSourceType同樣返回true時,才會調(diào)用該監(jiān)聽內(nèi)的onApplicationEvent方法
*
* @param aClass 接收到的監(jiān)聽事件類型
* @return
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
//只有OrderEvent監(jiān)聽類型才會執(zhí)行下面邏輯
return aClass == OrderCancelEvent.class;
}
/**
* 該方法返回true&supportsEventType同樣返回true時佩捞,才會調(diào)用該監(jiān)聽內(nèi)的onApplicationEvent方法
*
* @param aClass
* @return
*/
@Override
public boolean supportsSourceType(Class<?> aClass) {
//只有在OmsOrderServiceImpl內(nèi)發(fā)布的UserRegisterEvent事件時才會執(zhí)行下面邏輯
return aClass == OmsOrderServiceImpl.class;
}
/**
* supportsEventType & supportsSourceType 兩個方法返回true時調(diào)用該方法執(zhí)行業(yè)務邏輯
*
* @param applicationEvent 具體監(jiān)聽實例绞幌,這里是orderEvent
*/
@Override
@Async
public void onApplicationEvent(ApplicationEvent applicationEvent) {
log.debug("————訂單超時關(guān)閉事件開始準備,倒計時1分鐘");
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
//轉(zhuǎn)換事件類型
OrderCancelEvent orderCancelEvent = (OrderCancelEvent) applicationEvent;
//獲取訂單對象
OmsOrder order = orderCancelEvent.getOmsOrder();
Runnable payTimeoutTask = new Runnable() {
@Override
public void run() {
//執(zhí)行訂單超時一忱,系統(tǒng)取消訂單操作
orderService.cancelOrderAutoById(order.getId());
}
};
executor.schedule(payTimeoutTask, 2, TimeUnit.MINUTES);
try {
//每分鐘檢查任務是否完成莲蜘,完成后關(guān)閉任務線程
executor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
log.debug("————訂單超時關(guān)閉事件執(zhí)行結(jié)束");
}
public void SyncAndAsync() throws ExecutionException, InterruptedException {
AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
System.out.println("First");
//Future<String> future = executor.submit(new CallableTask("Second")); //同步
executor.execute(new RunnableTask("go!"));
//System.out.println(future.get());
System.out.println("Third");
}
class RunnableTask implements Runnable{
private String parameter;
public RunnableTask(String parameter) {
super();
this.parameter = parameter;
}
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
System.out.println(parameter+ "Second");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class CallableTask implements Callable<String> {
private String parameter;
public CallableTask(String parameter) {
super();
this.parameter = parameter;
}
@Override
public String call() throws Exception {
Thread.sleep(5 * 1000);
return parameter+ " finished!";
}
}
/**
* 同步情況下監(jiān)聽執(zhí)行的順序
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
SmartApplicationListener接口繼承了全局監(jiān)聽ApplicationListener,并且泛型對象使用的ApplicationEvent來作為全局監(jiān)聽帘营,可以理解為使用SmartApplicationListener作為監(jiān)聽父接口的實現(xiàn)票渠,監(jiān)聽所有事件發(fā)布。
既然是監(jiān)聽所有的事件發(fā)布芬迄,那么SmartApplicationListener接口添加了兩個方法supportsEventType问顷、supportsSourceType來作為區(qū)分是否是我們監(jiān)聽的事件,只有這兩個方法同時返回true時才會執(zhí)行onApplicationEvent方法禀梳。
可以看到除了上面的方法杜窄,還提供了一個getOrder方法,這個方法就可以解決執(zhí)行監(jiān)聽的順序問題算途,return的數(shù)值越小證明優(yōu)先級越高塞耕,執(zhí)行順序越靠前。
2嘴瓤、4 使用@Async實現(xiàn)異步監(jiān)聽
@Aysnc其實是Spring內(nèi)的一個組件扫外,可以完成對類內(nèi)單個或者多個方法實現(xiàn)異步調(diào)用,這樣可以大大的節(jié)省等待耗時纱注。內(nèi)部實現(xiàn)機制是線程池任務ThreadPoolTaskExecutor
畏浆,通過線程池來對配置@Async的方法或者類做出執(zhí)行動作胆胰。
2狞贱、5 具體業(yè)務場景
- 訂單創(chuàng)建時,發(fā)布
超時未支付自動關(guān)閉
事件:
@Service
public class OrderService
{
@Autowired
private ApplicationContext applicationContext;
/**
* 生成訂單
* @param order 訂單對象
*/
public void generateOrder(OmsOrder order)
{
//....省略邏輯
//發(fā)布訂單超時關(guān)閉事件
applicationContext.publishEvent(new OrderCancelEvent(this, order));
}
}
- 成功付款時蜀涨,發(fā)布
推送消息
事件:
@Service
public class OrderService
{
@Autowired
private ApplicationContext applicationContext;
/**
* 支付訂單
* @param order 訂單對象
*/
public void pay(OmsOrder order)
{
//....省略邏輯
// 發(fā)送支付成功消息事件
Map<String, Object> eventMap = Maps.newHashMap();
eventMap.put("order", order);
applicationContext.publishEvent(new MsgOrderPayEvent(this, eventMap));
}
}
2.6 拓展
上述2.5中描述了一個場景:訂單創(chuàng)建時瞎嬉,發(fā)布超時未支付自動關(guān)閉
事件,在這里厚柳,介紹幾種電商項目中如何處理超時未支付自動關(guān)閉訂單的方案:
- 第一種:數(shù)據(jù)庫加兩個字段, 一個字段標記: 是否付款,一個字段標記:過期時間氧枣,查詢時去判斷是否 付款和超時,然后更新狀態(tài)别垮。但是這種方案導致
占用商品資源
便监。 - 第二種:定時任務一直掃描,掃描到滿足條件的就進行更新操作。但是這種方案導致
不能準確處理訂單狀態(tài)
烧董。 - 第三種:TODO 依賴于第三方框架毁靶,比如框架Quartz、rabbitMQ等逊移,不太懂预吆,就不解釋了哈。
我目前是這樣處理超時未支付自動關(guān)閉:當訂單創(chuàng)建完成時胳泉,發(fā)布超時未支付自動關(guān)閉
事件拐叉,同時系統(tǒng)中還有一個每1分鐘執(zhí)行一次的掃描超時未支付訂單
的定時任務,兩種方案結(jié)合目前是可以解決上述問題扇商。采用異步事件
和定時任務
結(jié)合的方案凤瘦,有以下好處:
- 更加準確的處理超時未支付訂單,及時釋放庫存
- 防止系統(tǒng)宕機導致的進程丟失案铺,原有的事件任務無法執(zhí)行廷粒,通過定時任務可以有效解決此問題
三、小結(jié)
使用事件驅(qū)動模型可以大大降低我們實際項目中的代碼耦合红且,降低了前后端交互的響應耗時坝茎,而且還減少了后期業(yè)務變更引起的代碼調(diào)整的難度。具體使用步驟如下:
- 定義事件
- 創(chuàng)建事件監(jiān)聽器
- 業(yè)務代碼中發(fā)布事件