SpringBoot 中發布ApplicationEventPublisher,監聽ApplicationEvent 非同步操作

  • 2021 年 11 月 9 日
  • 筆記
有這麼一個業務場景:當用戶註冊後,發送郵件到其郵箱提示用戶進行帳號激活,且註冊成功的同時需要贈送新人用戶體驗卡券。

在這裡插入圖片描述

業務有了,那麼問題也就來了。

What? 問題….問題?我聽說你有問題? 來拔刀吧,互相傷害啊。
在這裡插入圖片描述
考慮以下兩個問題:如何註冊成功立即發送郵件、贈送體驗卡? 如何同時向用戶郵箱發送激活郵件、贈送新人客戶體驗卡,互不影響?
在這裡插入圖片描述
如果是微服務項目,該邏輯可結合消息中間件進行處理。若是單機程式碼,有什麼好的辦法哇?你還在瘋狂的程式碼邏輯判斷嗎?若程式碼高耦合,後期進行維護彷彿並不是那麼happy,就不用說在此基礎上擴展業務了。

So 請了解下Spring中事件機制:發布ApplicationEventPublisher,實現監聽ApplicationEvent。結合非同步操作,哎呀,真香!你值得擁有!
在這裡插入圖片描述
下面就跟著樓主的小碎步,慢慢帶你帶入坑。「氣死我了,上才藝。EG埃meng,EG埃meng,EG埃meng。你說我是…..」

說歸說,鬧歸鬧,不拿程式碼開玩笑。回歸正題,直接上程式碼。哇哈哈哈哈哈哈……

一、 首先定義下用戶類:

樓主示例這個用戶類屬性寫的比較隨意,只做測試看效果哈。
在這裡插入圖片描述

二、定義一個Event事件類:

注意:自定義事件類繼承ApplicationEvent類,重寫方法
在這裡插入圖片描述
該類中屬性根據業務需求自定義即可。

如下所示,樓主定義的Event類叫做UserActionEvent。
在這裡插入圖片描述
EnumUserOperate 枚舉類
在這裡插入圖片描述

三、事件類定義好了,我們去定義操作發布:ApplicationEventPublisher,快點跟上別掉隊了。

在這裡插入圖片描述

我是在UserServiceImpl中進行事件發布的,如下:
在這裡插入圖片描述
發布者會調用 ApplicationEventPublisher的publishEvent 方法對某一事件進行發布。隨後Spring容器會把該事件告訴所有的監聽者(我的「女神」有動態了),監聽者根據拿到的「資訊、某些指令或者某些數據」去做一些業務上的操作。

這個模式常常會與設計模式中觀察者模式進行對比。舉個栗子:上課鈴響了,老師和同學聽到鈴聲後,都來班裡了(老師要上課,學生要聽課)。在這個事件里,被觀察的是「鈴聲」,「鈴聲響了」是一種狀態,或者說是一種通知。告訴大家:該上課了。

四、發布事件後該定義監聽了:

自定義監聽方法上方添加註解:@EventListener()。

眼尖的小夥伴會發現,樓主這裡使用表達式condition = “#event.operate.name()==’ADD'”對監聽進行了細化:監聽類型為「新增」的事件

注意:自定義監聽必須交給spring容器管理,否則不起作用哈。如下圖加@Component註解就行(兄弟,交保護費了。額….不交也行,但是必須得跟著spring混….)

@Async()會在下面說
在這裡插入圖片描述
發布和監聽都設置好了,使用快樂的postman發送下請求……
在這裡插入圖片描述
測試結果如下:
在這裡插入圖片描述
加了表達式的只會監聽到指定類型的事件。當然這裡你可以加別的條件,根據業務怎麼開心怎麼來嘛,對不?

在這裡插入圖片描述

發布和監聽可以了,那我並發操作的時候如何保證不會阻塞,互不影響呢?

非同步啊,在加個執行緒池。

問一句:「老哥,為啥加執行緒池?」

多執行緒操作,反覆創建銷毀,性能消耗是很大的。使用執行緒池降低資源消耗,提高利用率,加上非同步操作速度還快,何樂而不為呢。

五、方法非同步:

定義方法上方加@Async()註解就好了。

非同步方法可以指定使用某一執行緒池:如 @Async(“lazyTraceExecutor”),lazyTraceExecutor是執行緒池Bean對象的名字

六、執行緒池自定義:

不知道有沒有人diss樓主只截圖,不貼程式碼。這不,他來了他來了….

@Configuration
public class Configurer implements AsyncConfigurer {

//    @Autowired
//    private BeanFactory beanFactory;

    /**
     * 自定義執行緒池
     *
     * @return
     */
    @Bean("lazyTraceExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //此方法返回可用處理器的虛擬機的最大數量; 不小於1
        int core = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(core);//設置核心執行緒數
        executor.setMaxPoolSize(core * 2 + 1);//設置最大執行緒數
        executor.setKeepAliveSeconds(3);//除核心執行緒外的執行緒存活時間
        executor.setQueueCapacity(40);//如果傳入值大於0,底層隊列使用的是LinkedBlockingQueue,否則默認使用SynchronousQueue
        executor.setThreadNamePrefix("my-executor-");//執行緒名稱前綴
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//設置拒絕策略
//        return new LazyTraceThreadPoolTaskExecutor(beanFactory, executor);
        executor.initialize();

        return executor;
    }
}

最後,別忘了在啟動項上加@EnableAsync註解哦!

想了解ApplicationEventPublisher和ApplicationEvent原理的,認準spring官網happy哈。這裡就不過多介紹了。

傲嬌的wshanshi要go to sleep了。

在這裡插入圖片描述
靚女,帥仔。你有沒有那個,那個小心心…. 沒有!呸,渣男。啥也不是,散會!

示例程式碼可以點擊此處下載:戳我戳我