Spring容器的事件監(jiān)聽機(jī)制(簡單明了的介紹)
文章目錄
前言
事件
1. 定義事件
2. 定義監(jiān)聽器
3. 定義發(fā)布器
Spring容器的事件監(jiān)聽機(jī)制
1.事件的繼承類圖
監(jiān)聽器的繼承類圖
總結(jié)
前言
上一篇我們介紹了SpringFactoriesLoader。這一篇我接著來介紹一下Spring的另一個(gè)知識點(diǎn),就是Spring容器的事件監(jiān)聽機(jī)制。
事件
說到事件,我們第一反應(yīng)是什么是事件?其實(shí) 事件是發(fā)生在應(yīng)用程序中的動(dòng)作,比如點(diǎn)擊按鈕,在文本框中輸入內(nèi)容等操作都被稱為事件。而當(dāng)事件觸發(fā)時(shí),應(yīng)用程序做出的一定的響應(yīng)則表示應(yīng)用監(jiān)聽了這個(gè)事件,而在服務(wù)器端,事件的監(jiān)聽機(jī)制更多的用于異步通知以及監(jiān)控和異常處理。Java提供了實(shí)現(xiàn)事件監(jiān)聽機(jī)制的兩個(gè)基礎(chǔ)類:自定義事件類型擴(kuò)展自java.util.EventObject,事件的監(jiān)聽器擴(kuò)展自java.util.EnventListener。下面我們就以一個(gè)監(jiān)控方法的耗時(shí)為例。
- 定義事件
首先自定義事件類型,通常的做法是繼承EnventObject類,隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常封裝在此類中。在此處我們定義了一個(gè)時(shí)間戳,用于記錄方法的開始執(zhí)行時(shí)間。
/**
-
定義事件類型,通常的做法是繼承 EnventObject,
-
隨著事件的發(fā)生,相應(yīng)的狀態(tài)通常都封裝在此類中。
-
@author xiang.wei
-
@date 2020/5/5 5:13 PM
*/
public class MethodMonitorEvent extends EventObject {/**
- 時(shí)間戳,用于記錄方法開始執(zhí)行的時(shí)間
*/
protected long timestamp;
/**
- Constructs a prototypical Event.
- @param source The object on which the Event initially occurred.
- @throws IllegalArgumentException if source is null.
*/
public MethodMonitorEvent(Object source) {
super(source);
}
}
- 時(shí)間戳,用于記錄方法開始執(zhí)行的時(shí)間
- 定義監(jiān)聽器
事件類型定義好之后,接下來,我們還需要定義一個(gè)監(jiān)聽器,用于監(jiān)聽事件的發(fā)生。我們可以在方法開始執(zhí)行之前發(fā)布一個(gè)begin事件,在方法結(jié)束之后發(fā)布一個(gè)end事件。我們定義了事件監(jiān)聽接口,里面定義了處理begin事件的方法onMethodBegin和處理end事件的方法onMethodEnd。我們注意到這兩個(gè)方法都只有一個(gè)參數(shù),就是MethodMonitorEvent 參數(shù)。說明這個(gè)監(jiān)聽器類只負(fù)責(zé)監(jiān)聽對應(yīng)的事件并進(jìn)行處理。
/**
-
定義事件監(jiān)聽接口
-
@author xiang.wei
-
@date 2020/5/5 5:24 PM
*/
public interface MethodMonitorEventListener extends EventListener {/**
- 處理方法執(zhí)行之前發(fā)布的事件
- @param event
*/
void onMethodBegin(MethodMonitorEvent event);
/**
- 處理方法結(jié)束時(shí)發(fā)布的事件
- @param event
*/
void onMethodEnd(MethodMonitorEvent event);
}
在這個(gè)監(jiān)聽器接口的實(shí)現(xiàn)類里我們將會具體實(shí)現(xiàn)這兩個(gè)方法的邏輯。方法也是很簡單
public class MethodMonitorEventListenerImpl implements MethodMonitorEventListener {
@Override
public void onMethodBegin(MethodMonitorEvent event) {
//記錄方法開始執(zhí)行時(shí)的時(shí)間
event.timestamp = System.currentTimeMillis();
}
@Override
public void onMethodEnd(MethodMonitorEvent event) {
//計(jì)算耗時(shí)
long duration = System.currentTimeMillis() - event.timestamp;
System.out.println("總耗時(shí):" + duration+" ms");
}
}
- 定義發(fā)布器
有了事件和監(jiān)聽器。我們還需要一個(gè)事件發(fā)布者,它本身作為一個(gè)事件源,在合適的時(shí)機(jī),將相應(yīng)的時(shí)間發(fā)布給對應(yīng)的事件監(jiān)聽器。
public class MethodMonitorEventPublisher {
private List<MethodMonitorEventListener> eventListeners = new ArrayList<>();
public void methodMonitor() throws InterruptedException {
//定義事件
MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
publishEvent("begin", eventObject);
//模擬方法執(zhí)行:休眠5秒鐘
TimeUnit.SECONDS.sleep(5);
publishEvent("end", eventObject);
}
//發(fā)布事件的邏輯
public void publishEvent(String status, MethodMonitorEvent event) {
//避免在事件處理期間,監(jiān)聽器被移除,這里為了安全做了一個(gè)復(fù)制操作
List<MethodMonitorEventListener> copyListeners = new ArrayList<>(eventListeners);
for (MethodMonitorEventListener listener : copyListeners) {
if ("begin".equals(status)) {
listener.onMethodBegin(event);
} else {
listener.onMethodEnd(event);
}
}
}
public static void main(String[] args) throws InterruptedException {
MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
//添加監(jiān)聽器
publisher.addEventListener(new MethodMonitorEventListenerImpl());
//發(fā)布事件
publisher.methodMonitor();
}
public void addEventListener(MethodMonitorEventListener listener) {
eventListeners.add(listener);
}
}
對于事件發(fā)布者(事件源)我們需要關(guān)注兩點(diǎn):
在合適的時(shí)機(jī)發(fā)布事件,此例中的methodMonitor()方法就是事件發(fā)布的源頭,其在方法執(zhí)行之前和結(jié)束之后兩個(gè)時(shí)間點(diǎn)發(fā)布MethodMonitorEvent事件,每個(gè)時(shí)間點(diǎn)發(fā)布的事件都會傳給相應(yīng)的監(jiān)聽器進(jìn)?處理。
事件監(jiān)聽器的管理:publisher 類中提供了事件監(jiān)聽器的注冊和移除方法。這樣客戶端可以根據(jù)實(shí)際情況決定是否需要注冊新的監(jiān)聽器或者移除某個(gè)監(jiān)聽器,如果沒有提供remove方法,那么注冊的監(jiān)聽器實(shí)例將一直被MethodMonitorEventPublisher引?,即使已經(jīng)廢棄不?了,也依然在發(fā)布者的監(jiān)聽器列表中,這會導(dǎo)致隱性的內(nèi)存泄漏。
Spring容器的事件監(jiān)聽機(jī)制
說完了Java提供的事件監(jiān)聽機(jī)制的兩個(gè)基礎(chǔ)類,以及如何實(shí)現(xiàn)一個(gè)自定義事件的demo。下面就請出本篇文章的主角Spring容器的時(shí)間監(jiān)聽機(jī)制。
Spring的ApplicationContext容器內(nèi)部中的所有事件類型均繼承自org.springframework.context.ApplicationEvent
容器中的所有監(jiān)聽器都實(shí)現(xiàn)了org.springframework.context.ApplicationListener接口
并且以bean的形式注冊到了容器中,一旦容器內(nèi)發(fā)布ApplicationEvent及其子類型的事件,注冊到容器中的ApplicationListener就會對這些事件進(jìn)行處理。ApplicationEventMulticaster類是事件管理者,管理監(jiān)聽器和發(fā)布事件,ApplicationContext通過委托ApplicationEventMulticaster來發(fā)布事件
ApplicationEventPublisher 是事件發(fā)布者,該接口封裝了事件有關(guān)的公共方法。
1.事件的繼承類圖
ApplicationContextEvent 繼承自ApplicationEvent,而ApplicationEvent 繼承自EventObject,Spring提供了一些默認(rèn)的實(shí)現(xiàn),比如:
ContextStartedEvent 表示容器在啟動(dòng)時(shí)發(fā)布的事件類型,即調(diào)用start()方法。
ContextRefreshedEvent表示容器在初始化或者刷新的時(shí)候發(fā)布的事件類型,如調(diào)用refresh() 方法,此處的實(shí)例化是指所有的bean都已經(jīng)被加載。后置處理器被激活。所有單例bean都已被實(shí)例化。所有的容器對象都已準(zhǔn)備好可使用。
ContextStoppedEvent表示容器在即將關(guān)閉時(shí)發(fā)布的事件類型,即調(diào)用了stop()方法。
監(jiān)聽器的繼承類圖
容器內(nèi)部用ApplicationListener作為事件監(jiān)聽器接口定義,它繼承自EventListener。ApplicationContext容器在啟動(dòng)時(shí),會自動(dòng)識別并加載EventListener類型的bean,一旦容器內(nèi)有事件發(fā)布,將通知這些注冊到容器的EventListener。
其中ContextRefreshListener監(jiān)聽器監(jiān)聽的是ContextRefreshedEvent事件,而ContextCloserListener監(jiān)聽器監(jiān)聽的是ContextClosedEvent事件。
private class ContextRefreshListener implements ApplicationListener {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
而ApplicationContext接口繼承了ApplicationEventPublisher接口,該接口提供了void
publishEvent(ApplicationEvent
event)方法的定義,不難看出,ApplicationContext容器擔(dān)當(dāng)?shù)木褪鞘录l(fā)布者的角色。需要說明的是Spring事件默認(rèn)是同步的,即調(diào)用publishEvent方法發(fā)布事件后,它會處于阻塞狀態(tài),直到onApplicationEvent接受到事件并處理返回之后才繼續(xù)執(zhí)行下去,這種單線程同步的好處是可以進(jìn)行任務(wù)管理。
總結(jié)
本文首先介紹了Java中事件監(jiān)聽機(jī)制的基本概念,并且以一個(gè)記錄方法耗時(shí)的demo說明了如何自定義事件類型。接著就是介紹了Spring容器的事件監(jiān)聽機(jī)制。
作者:碼農(nóng)飛哥
微信公眾號:碼農(nóng)飛哥