Spring容器的事件監(jiān)聽機制(簡單明了的介紹)

文章目錄

    前言
    事件
        1. 定義事件
        2. 定義監(jiān)聽器
        3. 定義發(fā)布器
    Spring容器的事件監(jiān)聽機制
        1.事件的繼承類圖
        監(jiān)聽器的繼承類圖
    總結

前言

上一篇我們介紹了SpringFactoriesLoader。這一篇我接著來介紹一下Spring的另一個知識點,就是Spring容器的事件監(jiān)聽機制。
事件

說到事件,我們第一反應是什么是事件?其實 事件是發(fā)生在應用程序中的動作,比如點擊按鈕,在文本框中輸入內容等操作都被稱為事件。而當事件觸發(fā)時,應用程序做出的一定的響應則表示應用監(jiān)聽了這個事件,而在服務器端,事件的監(jiān)聽機制更多的用于異步通知以及監(jiān)控和異常處理。Java提供了實現(xiàn)事件監(jiān)聽機制的兩個基礎類:自定義事件類型擴展自java.util.EventObject,事件的監(jiān)聽器擴展自java.util.EnventListener。下面我們就以一個監(jiān)控方法的耗時為例。

  1. 定義事件

首先自定義事件類型,通常的做法是繼承EnventObject類,隨著事件的發(fā)生,相應的狀態(tài)通常封裝在此類中。在此處我們定義了一個時間戳,用于記錄方法的開始執(zhí)行時間。

/**

  • 定義事件類型,通常的做法是繼承 EnventObject,

  • 隨著事件的發(fā)生,相應的狀態(tài)通常都封裝在此類中。

  • @author xiang.wei

  • @date 2020/5/5 5:13 PM
    */
    public class MethodMonitorEvent extends EventObject {

    /**

    • 時間戳,用于記錄方法開始執(zhí)行的時間
      */
      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);
      }
      }
  1. 定義監(jiān)聽器

事件類型定義好之后,接下來,我們還需要定義一個監(jiān)聽器,用于監(jiān)聽事件的發(fā)生。我們可以在方法開始執(zhí)行之前發(fā)布一個begin事件,在方法結束之后發(fā)布一個end事件。我們定義了事件監(jiān)聽接口,里面定義了處理begin事件的方法onMethodBegin和處理end事件的方法onMethodEnd。我們注意到這兩個方法都只有一個參數(shù),就是MethodMonitorEvent 參數(shù)。說明這個監(jiān)聽器類只負責監(jiā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);

    /**

    • 處理方法結束時發(fā)布的事件
    • @param event
      */
      void onMethodEnd(MethodMonitorEvent event);
      }

在這個監(jiān)聽器接口的實現(xiàn)類里我們將會具體實現(xiàn)這兩個方法的邏輯。方法也是很簡單

public class MethodMonitorEventListenerImpl implements MethodMonitorEventListener {
@Override
public void onMethodBegin(MethodMonitorEvent event) {
//記錄方法開始執(zhí)行時的時間
event.timestamp = System.currentTimeMillis();
}

@Override
public void onMethodEnd(MethodMonitorEvent event) {
    //計算耗時
    long duration = System.currentTimeMillis() - event.timestamp;
    System.out.println("總耗時:" + duration+" ms");
}

}

  1. 定義發(fā)布器

有了事件和監(jiān)聽器。我們還需要一個事件發(fā)布者,它本身作為一個事件源,在合適的時機,將相應的時間發(fā)布給對應的事件監(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)聽器被移除,這里為了安全做了一個復制操作
    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ā)布者(事件源)我們需要關注兩點:

在合適的時機發(fā)布事件,此例中的methodMonitor()方法就是事件發(fā)布的源頭,其在方法執(zhí)行之前和結束之后兩個時間點發(fā)布MethodMonitorEvent事件,每個時間點發(fā)布的事件都會傳給相應的監(jiān)聽器進?處理。
事件監(jiān)聽器的管理:publisher 類中提供了事件監(jiān)聽器的注冊和移除方法。這樣客戶端可以根據(jù)實際情況決定是否需要注冊新的監(jiān)聽器或者移除某個監(jiān)聽器,如果沒有提供remove方法,那么注冊的監(jiān)聽器實例將一直被MethodMonitorEventPublisher引?,即使已經廢棄不?了,也依然在發(fā)布者的監(jiān)聽器列表中,這會導致隱性的內存泄漏。

Spring容器的事件監(jiān)聽機制

說完了Java提供的事件監(jiān)聽機制的兩個基礎類,以及如何實現(xiàn)一個自定義事件的demo。下面就請出本篇文章的主角Spring容器的時間監(jiān)聽機制。
Spring的ApplicationContext容器內部中的所有事件類型均繼承自org.springframework.context.ApplicationEvent
容器中的所有監(jiān)聽器都實現(xiàn)了org.springframework.context.ApplicationListener接口
并且以bean的形式注冊到了容器中,一旦容器內發(fā)布ApplicationEvent及其子類型的事件,注冊到容器中的ApplicationListener就會對這些事件進行處理。ApplicationEventMulticaster類是事件管理者,管理監(jiān)聽器和發(fā)布事件,ApplicationContext通過委托ApplicationEventMulticaster來發(fā)布事件
ApplicationEventPublisher 是事件發(fā)布者,該接口封裝了事件有關的公共方法。
1.事件的繼承類圖

ApplicationContextEvent 繼承自ApplicationEvent,而ApplicationEvent 繼承自EventObject,Spring提供了一些默認的實現(xiàn),比如:
ContextStartedEvent 表示容器在啟動時發(fā)布的事件類型,即調用start()方法。
ContextRefreshedEvent表示容器在初始化或者刷新的時候發(fā)布的事件類型,如調用refresh() 方法,此處的實例化是指所有的bean都已經被加載。后置處理器被激活。所有單例bean都已被實例化。所有的容器對象都已準備好可使用。
ContextStoppedEvent表示容器在即將關閉時發(fā)布的事件類型,即調用了stop()方法。
監(jiān)聽器的繼承類圖













容器內部用ApplicationListener作為事件監(jiān)聽器接口定義,它繼承自EventListener。ApplicationContext容器在啟動時,會自動識別并加載EventListener類型的bean,一旦容器內有事件發(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容器擔當?shù)木褪鞘录l(fā)布者的角色。需要說明的是Spring事件默認是同步的,即調用publishEvent方法發(fā)布事件后,它會處于阻塞狀態(tài),直到onApplicationEvent接受到事件并處理返回之后才繼續(xù)執(zhí)行下去,這種單線程同步的好處是可以進行任務管理。
總結

本文首先介紹了Java中事件監(jiān)聽機制的基本概念,并且以一個記錄方法耗時的demo說明了如何自定義事件類型。接著就是介紹了Spring容器的事件監(jiān)聽機制。





作者:碼農飛哥
微信公眾號:碼農飛哥