【Springboot深入解析】監聽器

監聽器

一、監聽器模式介紹

監聽器模式可以用下圖來體現:
在這裏插入圖片描述

當運行到關鍵節點的時候,系統會通過廣播器發佈相應的事件,而我們系統中存在一些監聽器,它們會訂閱一些“感興趣”(實際是人爲設定)的事件。

當有事件發佈之後,相關的監聽器會監聽到訂閱的事件,進而觸發相關的“行爲”(實際上就是處理事件)。

其實可以理解成 發佈-訂閱的一種形式。

監聽器模式主要有四個要素

  • 事件
  • 監聽器
  • 廣播器
  • 觸發機制

下面我們手動實現一下:

事件對象

創建一個天氣事件的抽象類

public abstract class WeatherEvent {

	// 獲得當前天氣
    public abstract String getWeather();

}

接下來定義兩個事件,下雨事件和下雪事件。

public class RainEvent extends WeatherEvent {
    @Override
    public String getWeather() {
        return "rain";
    }
}
public class SnowEvent extends WeatherEvent {
    @Override
    public String getWeather() {
        return "snow";
    }
}

監聽器

下面定義我們的事件監聽器

public interface WeatherListener {

    void onWeatherEvent(WeatherEvent event);
}

下雪的事件監聽器

import org.springframework.stereotype.Component;

@Component
public class SnowListener implements WeatherListener {
    @Override
    public void onWeatherEvent(WeatherEvent event) {
        if (event instanceof SnowEvent) {
            System.out.println("hello " + event.getWeather());
        }
    }
}

下雨的事件監聽器

import org.springframework.stereotype.Component;

@Component
public class RainListener implements WeatherListener {
    @Override
    public void onWeatherEvent(WeatherEvent event) {
        if (event instanceof RainEvent) {
            System.out.println("hello " + event.getWeather());
        }
    }
}

廣播器

下面定義我們的事件廣播器

public interface EventMulticaster {
	
	// 廣播事件
    void multicastEvent(WeatherEvent event);
	
	// 添加監聽器
    void addListener(WeatherListener weatherListener);

	//移除監聽器
    void removeListener(WeatherListener weatherListener);

}

定義一個模板類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public abstract class AbstractEventMulticaster implements EventMulticaster {

    private List<WeatherListener> listenerList = new ArrayList<>();

    @Override
    public void multicastEvent(WeatherEvent event) {
        doStart();
        listenerList.forEach(i -> i.onWeatherEvent(event));
        doEnd();
    }

    @Override
    public void addListener(WeatherListener weatherListener) {
        listenerList.add(weatherListener);
    }

    @Override
    public void removeListener(WeatherListener weatherListener) {
        listenerList.remove(weatherListener);
    }

    abstract void doStart();

    abstract void doEnd();

}

定義實現類

import org.springframework.stereotype.Component;

@Component
public class WeatherEventMulticaster extends AbstractEventMulticaster {

    @Override
    void doStart() {
        System.out.println("begin broadcast weather event");
    }

    @Override
    void doEnd() {
        System.out.println("end broadcast weather event");
    }
}

測試方法 含觸發機制

public class Test {

    public static void main(String[] args) {
        WeatherEventMulticaster eventMulticaster = new WeatherEventMulticaster();
        RainListener rainListener = new RainListener();
        SnowListener snowListener = new SnowListener();
        eventMulticaster.addListener(rainListener);
        eventMulticaster.addListener(snowListener);
        eventMulticaster.multicastEvent(new SnowEvent());
        eventMulticaster.multicastEvent(new RainEvent());
        eventMulticaster.removeListener(rainListener);
        eventMulticaster.multicastEvent(new SnowEvent());
        eventMulticaster.multicastEvent(new RainEvent());
    }
}

結果:

begin broadcast weather event
hello snow
end broadcast weather event
begin broadcast weather event
hello rain
end broadcast weather event
begin broadcast weather event
hello snow
end broadcast weather event
begin broadcast weather event
end broadcast weather event

二、系統監聽器介紹

下面看一下Springboot中的監聽器是什麼樣的情況。

springboot中的系統監聽器是ApplicationListener
在這裏插入圖片描述
springboot的監聽器通過繼承這個接口來實現監聽器,這個接口是基於監聽器模式的標準進行設計的。在spring3.0之後,監聽器可以訂閱自己感興趣的事件,當監聽器注入到spring容器中之後,在程序運行的一些關鍵節點,會有事件的發生,訂閱相關事件的系統監聽器會被觸發。

這個接口繼承自EventListener ,看到源碼,你會發現這個接口僅僅是一個聲明。

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

回到ApplicationListener。你會看到函數式接口的註解,我之間寫了一篇關於Lamdda表達式的長文,你可以找來看看。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);
}

下面看一下系統廣播器
在這裏插入圖片描述
這個接口主要就是管理事件監聽器並且廣播事件

public interface ApplicationEventMulticaster {

	// 添加一個系統監聽器
	void addApplicationListener(ApplicationListener<?> listener);
	
	// 同上 不過是通過全限定類名獲取
	void addApplicationListenerBean(String listenerBeanName);

	// 移除相關係統監聽器
	void removeApplicationListener(ApplicationListener<?> listener);

	// 同上 不過是通過全限定類名獲取
	void removeApplicationListenerBean(String listenerBeanName);

	// 移除所有系統監聽器
	void removeAllListeners();
	
	// 下面兩個都是廣播事件的方法
	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

系統事件

在這裏插入圖片描述
最頂層是EventObject代表事件對象,ApplicationEvent代表應用事件。SpringApplicationEvent代表是Spring當中的應用事件,它這這種集成體系實現了分層描述。

  • ApplicationPreparedEvent – 代表應用準備好
  • ApplicationStartingEvent – 代表應用已經啓動
  • ApplicationFailedEvent – 代表應用失敗

這與剛纔定義的下雪事件和下雨事件類似。每一個Event都對應一類事件。

事件發送順序

在這裏插入圖片描述

  1. 首先框架啓動之後,會發生start事件
  2. 在環境準備好之後觸發environmentPrepared事件。(相關屬性已經加載到容器內)
  3. springboot準備好上下文之後,觸發contextInitialized事件
  4. 應用上下文創建完畢,但是bean還沒有加載完成,這個時候會觸發prepared事件
  5. 當bean實例化完成之後,但是還沒有調用SpringApplication的run方法,會觸發started事件
  6. 當run方法調用之後,會觸發ready事件

在整個過程中,如果出現失敗會觸發failed事件。

監聽器註冊

在系統初始化器那一篇文章中,我們通過看源碼,發現系統初始化器的是通過加載META-INF目錄下的spring.factories文件實現最終注入到spring容器的。

我們用相同的方式去探究:
在這裏插入圖片描述
一直定位一下去,發現這五行代碼基本一樣,是採用SpringFactoriesLoader.loadFactoryNames走的。
v
區別傳入的type不一樣。系統監聽器的是ApplicationListener.class
在這裏插入圖片描述
上述過程可以用這張圖來概述:
在這裏插入圖片描述

三:監聽事件觸發機制

第二部分,我們過了監聽器如何被加載的源碼,這一部分我們從run方法着手去探究事件觸發機制。

考慮到不同事件的觸發機制大同小異,這裏就以一個作爲入口,過一遍。

在這裏插入圖片描述
定位到listeners.starting()方法內部
在這裏插入圖片描述
我們看一下 SpringApplicationRunListener是什麼樣的
在這裏插入圖片描述
你會發現它定義了各個階段的事件。

我們回到starting()方法
在這裏插入圖片描述
通過debug可以看到,運行期類的是
在這裏插入圖片描述
定位到EventPubLishingRunListener的源碼,發現它調用了廣播器發送了相應的事件。
在這裏插入圖片描述
這種機制使得監聽器的內部實現與外部的調用隔離開了。


你可能在這裏有點迷惑,我在這裏的時候也迷惑了。以最初的例子爲例,進行一個改造。

我們定義一個 WeatherRunListener

@Component
public class WeatherRunListener {

    @Autowired
    private WeatherEventMulticaster eventMulticaster;

    public void snow() {
        eventMulticaster.multicastEvent(new SnowEvent());
    }

    public void rain() {
        eventMulticaster.multicastEvent(new RainEvent());
    }

}

改造之前的模板類,採用注入的形式。

    @Autowired
    private List<WeatherListener> listenerList;

我們同樣測試一下

@SpringBootTest
class DemoApplicationTests {


    @Autowired
    private WeatherRunListener weatherRunListener;

    @Test
    void contextLoads() {
        weatherRunListener.rain();
        weatherRunListener.snow();
    }
}

結果

begin broadcast weather event
hello rain
end broadcast weather event
begin broadcast weather event
hello snow
end broadcast weather event

這種方式 不是spring處理的基本上一樣嘛


接下來進入 調用廣播器的starting方法。

public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

進入multicastEvent方法,發現是同名方法。resolveDefaultEventType方法功能是獲取事件的class對象的包裝。
在這裏插入圖片描述
繼續走,發現這裏創建線程池去處理一些事情

在這裏插入圖片描述
定位getApplicationListeners(event, type)方法的源碼

protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {
 
 		//  獲得事件的來源Object source = event.getSource();  
 		// source其實是SpringApplication
        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        // 如果已經在緩存中存在感興趣的監聽器,直接返回
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
 
        // Quick check for existing entry on ConcurrentHashMap...
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }
 
        if (this.beanClassLoader == null ||
                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            // Fully synchronized building and caching of a ListenerRetriever
            synchronized (this.retrievalMutex) {
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }

這裏最核心的方法是retrieveApplicationListeners()方法

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
 
    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
	// 同步方法(防止多線程進入) 獲取spring容器加載進來的監聽器的實現
    synchronized (this.retrievalMutex) {
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    
    // 遍歷監聽器
    for (ApplicationListener<?> listener : listeners) {
    	// 是否對該事件感興趣(底層調用 instanceOf方法)
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
            	// 感興趣的話 會加入集合當中
                retriever.applicationListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }
    

    if (!listenerBeans.isEmpty()) {
        BeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                Class<?> listenerType = beanFactory.getType(listenerBeanName);
                if (listenerType == null || supportsEvent(listenerType, eventType)) {
                    ApplicationListener<?> listener =
                            beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                retriever.applicationListeners.add(listener);
                            }
                            else {
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Singleton listener instance (without backing bean definition) disappeared -
                // probably in the middle of the destruction phase
            }
        }
    }
	// 對監聽器進行一個排序(按照order值)
    AnnotationAwareOrderComparator.sort(allListeners);
    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
        retriever.applicationListeners.clear();
        retriever.applicationListeners.addAll(allListeners);
    }
    // 返回集合
    return allListeners;
}

getApplicationListeners(event, type) 方法其實就是 獲得對當前event感興趣的監聽器列表。
在這裏插入圖片描述
supportsEvent(listener, eventType, sourceType)方法的內部實現大概是這個流程,最核心的兩個方法是supportsEventType和supportsSourceType。
在這裏插入圖片描述

回到之前的步驟:我們現在已經獲取到當前event感興趣的監聽器列表。

下面依次進行觸發即可。
在這裏插入圖片描述

四:自定義監聽器

創建一個包 listener

定義我們的監聽器,方式與系統初始化器基本上一致,不再深入探討,看效果吧。

第一種方式:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;

@Order(1)
public class FirstListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("hello first");
    }
}

在META-INF下的spring.factories中添加自定義監聽器的全限定名

org.springframework.context.ApplicationListener=com.example.demo.listener.FirstListener

運行發現,註冊監聽器成功。
在這裏插入圖片描述
下面看第二種方式:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;

@Order(2)
public class SecondListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("hello second");
    }
}

更改啓動類

//        SpringApplication.run(DemoApplication.class, args);
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        springApplication.addListeners(new SecondListener());
        springApplication.run(args);

結果:
在這裏插入圖片描述
第三種方式:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;

@Order(3)
public class ThirdListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("hello third");
    }

}

在application.properties文件中添加

context.listener.classes=com.example.demo.listener.ThirdListener

啓動

在這裏插入圖片描述

第四種方式:

import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.annotation.Order;

@Order(4)
public class FourthListener implements SmartApplicationListener {

	// 對哪一些事件感興趣
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationStartedEvent.class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }

	// 事件發生後,我們做一些什麼事情
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("hello fourth");
    }
}

以上三種方式 任選一種注入到spring容器中,我這裏選擇第一種方式

context.listener.classes=com.example.demo.listener.ThirdListener,com.example.demo.listener.FourthListener

結果:
在這裏插入圖片描述
實現 Applicationlistene接口針對單一事件監聽

實現 SmartApplicationlistener接口針對多種事件監聽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章