Java?Spring?事件監(jiān)聽詳情解析
前言
前段時(shí)間因?yàn)楣ぷ鞯男枰玫絊pring事件,翻翻文檔將功能實(shí)現(xiàn)了,但是存在少許理解不暢的地方,今天有空來梳理梳理。
需求背景
葉子同學(xué)在新入職公司,老大讓他實(shí)現(xiàn)登陸功能,葉子隨手寫完,上線無bug,一切安好
//登陸偽代碼 public void login(....){ userLogin(....); }
幾天之后,老大說為維護(hù)用戶的粘度,每天登陸送積分。葉子同學(xué),二話不說,一頓操作后,上線無bug,一切安好
//登陸偽代碼 public void login(....){ //登陸 userLogin(....); //送積分 loginPoint(....) }
又幾天后,老大說,為了客戶安全,每次異地登陸發(fā)送郵件。葉子同學(xué)稍微抱怨,看在錢份上又是一頓操作后,上線無bug, 一切安好
//登陸偽代碼 public void login(....){ //登陸 userLogin(....); //送積分 loginPoint(....) //發(fā)送郵件 sendEmail(....) }
又又幾天后,老大說,部分客戶不用郵件,用短信。葉子同學(xué)壓著怒氣,看著銀行卡,又是一頓操作后,上線無bug, 一切安好
//登陸偽代碼 public void login(....){ //登陸 userLogin(....); //送積分 loginPoint(....) //發(fā)送郵件 sendEmail(....) //發(fā)短信 sendSms(...) }
又又又幾天后,老大還沒開口,葉子同學(xué)就忍無可忍啦,得加錢。老大哄了好久,說不改需求了。改bug,用戶抱怨登陸慢,有時(shí)還不成功。葉子二話不說,接手排查,查出問題啦
- 1> 郵件發(fā)送耗時(shí)
- 2>同步實(shí)現(xiàn),如果郵件發(fā)送超時(shí),登陸會(huì)出異常
代碼改進(jìn):
//登陸偽代碼 public void login(....){ //登陸 userLogin(....); try{ //送積分 new Thread(()->loginPoint(....)).start(); //發(fā)送郵件 new Thread(()->sendEmail(....)).start(); //發(fā)短信 new Thread(()->sendSms(....)).start(); }catch(Exception e){ //異常處理 } }
問題解決,功能實(shí)現(xiàn),ok~
又又又又幾天之后,老大說,只需要實(shí)現(xiàn)登陸功能即可,其他都不要~
葉子同學(xué)一句臥槽,然后是一段含母非常高的國粹。
此時(shí),問:如果你是葉子同學(xué),你有啥方案能優(yōu)雅應(yīng)對(duì)上面的需求變更呢?
一種優(yōu)雅方案:事件監(jiān)聽機(jī)制
事件概念
定義
事件監(jiān)聽機(jī)制:就是對(duì)一個(gè)事件(行為動(dòng)作)進(jìn)行監(jiān)聽,當(dāng)外界觸發(fā)某事件時(shí),監(jiān)聽程序馬上被捕獲該事件,并觸發(fā)相應(yīng)的響應(yīng),這過程稱之為事件監(jiān)聽機(jī)制。
組成
事件監(jiān)聽機(jī)制有3個(gè)核心組成部分:
- 1>事件,標(biāo)記某種行為動(dòng)作,比如:鼠標(biāo)點(diǎn)擊事件,鼠標(biāo)移動(dòng)事件等。
- 2>事件源,被監(jiān)控的對(duì)象或組件,事件發(fā)生的地方。比如:點(diǎn)擊按鈕,觸發(fā)點(diǎn)擊事件,按鈕就是實(shí)現(xiàn)源。
- 3>事件監(jiān)聽器,監(jiān)聽事件的操作類,一旦事件發(fā)生(被觸發(fā)),則執(zhí)行事件監(jiān)聽器預(yù)設(shè)的邏輯,進(jìn)行事件響應(yīng)。
- 1>定義事件,并綁定到事件源中
- 2>定義事件監(jiān)聽器,監(jiān)聽事件源
- 3>用戶某行為觸發(fā)事件
- 4>事件監(jiān)聽器監(jiān)控到事件發(fā)送,執(zhí)行事件響應(yīng)邏輯。
以上面的登錄為例:
- 事件源:login方法
- 事件:用戶login行為
- 事件監(jiān)聽器:此處沒有,需要額外定制,但是事件響應(yīng):送積分,發(fā)郵件,發(fā)短信。
事件實(shí)現(xiàn)
以登錄為例子實(shí)現(xiàn)事件監(jiān)聽機(jī)制
1>定義抽象事件
/** * 抽象事件類 * 作用:定制事件邏輯 */ public class AbstractEvent { //綁定的事件源 private Object source; public AbstractEvent(Object source) { this.source = source; } public Object getSource() { return source; } public void setSource(Object source) { this.source = source; } }
2>定制登陸事件
/** * 登陸事件 */ public class LoginEvent extends AbstractEvent{ public LoginEvent(Object source) { super(source); } }
3>定義事件監(jiān)聽器
/** * 事件監(jiān)聽器 * 作用:當(dāng)監(jiān)控的事件發(fā)送時(shí),執(zhí)行預(yù)設(shè)的邏輯 */ public interface EventListener<E extends AbstractEvent> { /** * 預(yù)設(shè)邏輯方法 * 事件被觸發(fā),馬上執(zhí)行 */ void onEvent(E event); }
4>定制登陸事件監(jiān)聽器
積分監(jiān)聽器
/** * 加積分監(jiān)聽器器: * 當(dāng)用戶登陸事件觸發(fā)后,馬上執(zhí)行 */ public class PointsListener implements EventListener<LoginEvent> { public void onEvent(LoginEvent event) { System.out.println(event.getSource() + "發(fā)生后,執(zhí)行積分+1操作"); System.out.println(Thread.currentThread().getName()); } }
短信監(jiān)聽器
/** * 加積分監(jiān)聽器器: * 當(dāng)用戶登陸事件觸發(fā)后,馬上執(zhí)行 */ public class SmsListener implements EventListener<LoginEvent> { public void onEvent(LoginEvent event) { System.out.println(event.getSource() + "發(fā)生后,執(zhí)行發(fā)送短信操作"); System.out.println(Thread.currentThread().getName()); } }
郵件監(jiān)聽器
/** * 加積分監(jiān)聽器器: * 當(dāng)用戶登陸事件觸發(fā)后,馬上執(zhí)行 */ public class EmailListener implements EventListener<LoginEvent> { public void onEvent(LoginEvent event) { try { //模擬10s延時(shí) Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(event.getSource() + "發(fā)生后,執(zhí)行發(fā)送郵件操作"); System.out.println(Thread.currentThread().getName()); } }
5>定義事件廣播器
/** * 事件廣播器 * 1>注冊(cè)事件監(jiān)聽器 * 2>刪除事件監(jiān)聽器 * 3>事件觸發(fā)時(shí),廣播事件 */ public interface EventMulticaster { //廣播事件 void multicastEvent(AbstractEvent event); //注冊(cè)事件監(jiān)聽器 void registListener(EventListener listener); //刪除事件監(jiān)聽器 void removeListener(EventListener listener); }
6>定制簡單的事件廣播器
/** * 事件廣播器實(shí)現(xiàn)類 * 作用:維護(hù)事件監(jiān)聽器 */ public class SimpleEventMulticaster implements EventMulticaster { //key:事件字節(jié)碼對(duì)象, value:當(dāng)前事件綁定的事件監(jiān)聽器 private Map<Class<?>, List<EventListener>> map = new HashMap<Class<?>, List<EventListener>>(); public void multicastEvent(AbstractEvent event) { List<EventListener> eventListeners = map.get(event.getClass()); if(eventListeners != null){ ExecutorService executorService = Executors.newCachedThreadPool(); for (EventListener eventListener : eventListeners) { //異步 executorService.submit(()-> eventListener.onEvent(event)); //同步 //eventListener.onEvent(event); } executorService.shutdown(); } } public void registListener(EventListener listener) { //獲取監(jiān)聽器綁定的事件 ParameterizedType getType = (ParameterizedType)listener.getClass().getGenericInterfaces()[0]; Type type = getType.getActualTypeArguments()[0]; Class<?> clz = (Class<?>) type; List<EventListener> listeners = map.get(clz); if(listeners == null){ listeners = new ArrayList<EventListener>(); map.put(clz, listeners); } listeners.add(listener); } public void removeListener(EventListener listener) { //獲取監(jiān)聽器綁定的事件 ParameterizedType getType = (ParameterizedType)listener.getClass().getGenericInterfaces()[0]; Type type = getType.getActualTypeArguments()[0]; Class<?> clz = (Class<?>) type; List<EventListener> listeners = map.get(clz); if(listener != null){ listeners.remove(listener); } } }
7>綜合測(cè)試
public class App { //1:初始化事件廣播器 public static SimpleEventMulticaster multicaster = new SimpleEventMulticaster(); static { //2:注冊(cè)監(jiān)聽器 //登陸事件上綁定3個(gè)監(jiān)聽器 multicaster.registListener(new PointsListener()); multicaster.registListener(new EmailListener()); multicaster.registListener(new SmsListener()); } //3:模擬登陸 public static void login(){ //4:用戶登陸成功觸發(fā)登陸事件 System.out.println("用戶執(zhí)行登陸邏輯"); System.out.println(Thread.currentThread().getName()); //5:廣播登陸事件 multicaster.multicastEvent(new LoginEvent("用戶登陸啦")); System.out.println("登陸成功....."); } public static void main(String[] args) { App.login(); System.out.println(Thread.currentThread().getName()); } }
時(shí)序圖
到此這篇關(guān)于Java Spring 事件監(jiān)聽詳情解析的文章就介紹到這了,更多相關(guān)Java 事件監(jiān)聽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Eclipse+Java+Swing+Mysql實(shí)現(xiàn)工資管理系統(tǒng)
這篇文章主要介紹了Eclipse+Java+Swing+Mysql實(shí)現(xiàn)工資管理系統(tǒng),對(duì)正在工作或者學(xué)習(xí)的你有一定的參考價(jià)值,需要的朋友可以參考一下2022-01-01SpringBoot選擇自有bean優(yōu)先加載實(shí)現(xiàn)方法
在一些需求中,可能存在某些場景,比如先加載自己的bean,然后自己的bean做一些DB操作,初始化配置問題,然后后面的bean基于這個(gè)配置文件,繼續(xù)做其他的業(yè)務(wù)邏輯。因此有了本文的這個(gè)題目2023-03-03Gradle環(huán)境下導(dǎo)出Swagger為PDF的步驟詳解
這篇文章主要介紹了Gradle環(huán)境下導(dǎo)出Swagger為PDF的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06簡單了解JAVA public class與class區(qū)別
這篇文章主要介紹了簡單了解JAVA public class與class區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03使用IDEA創(chuàng)建SpringBoot項(xiàng)目的方法步驟
這篇文章主要介紹了使用IDEA創(chuàng)建SpringBoot項(xiàng)目的方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05SpringCloud超詳細(xì)講解微服務(wù)網(wǎng)關(guān)Zuul
這篇文章主要介紹了SpringCloud Zuul微服務(wù)網(wǎng)關(guān),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07