有這么一個業務場景:當用戶注冊后,發送郵件到其郵箱提示用戶進行賬號激活,且注冊成功的同時需要贈送新人用戶體驗卡券。
業務有了,那么問題也就來了。
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了。
靚女,帥仔。你有沒有那個,那個小心心.... 沒有!呸,渣男。啥也不是,散會!
示例代碼可以點擊此處下載:戳我戳我