原始碼級別的廣播與監聽實現

阿Q說程式碼發表於2022-04-07

原創:微信公眾號 【阿Q說程式碼】,歡迎分享,轉載請保留出處。

近期疫情形勢嚴峻,情形不容樂觀,週末也不敢出去浪了,躲在家裡“葛優躺”。閒來無事,又翻了遍Spring的原始碼。不翻不知道,一翻嚇一跳,之前翻過的原始碼已經吃進了肚子裡,再見亦是陌生人。

個人建議:為了以後能快速的撿起某個知識點,最好的方法還是形成文件,下次有遺漏的時候,直接讀文件,按之前的思路捋一遍,“乾淨又衛生”。

之前的文章中我們已經介紹過如何在專案中快速上手“事件通知機制”,相信大家已經掌握了。但是我們作為高階javaer,要知其然,更要知其所以然。今天就帶大家從原始碼的角度來分析一下廣播與監聽的底層實現原理。

原始碼匯入教程也給你準備好了,不來試試嗎?

版本號:spring-framework-5.0.x

原始碼解析

為了實現廣播與監聽的功能,Spring為我們提供了兩個重要的函式式介面:ApplicationEventPublisherApplicationListener。前者的publishEvent()方法為我們提供了傳送廣播的能力;後者的onApplicationEvent()方法為我們提供了監聽並處理事件的能力。

接下來我們就來分析一下spring是如何運用這兩種能力的。

不知道大家對單例物件的初始化呼叫過程是否熟悉?主要呼叫方法流程如下:

傳送廣播

applyBeanPostProcessorsBeforeInitialization方法會去遍歷該工廠建立的所有的Bean後置處理器,然後去依次執行後置處理器對應的postProcessBeforeInitialization方法。

在該方法的實現類中我們看到了兩個熟悉的類名

不知道大家還記得不,這倆類是在beanFactory的準備工作過程中新增的兩個bean的後置處理器,所以這個地方會依次去執行這兩個類中的實現方法。

由於藍框中類的實現方法是預設實現按照原樣返回的給定的bean,所以此處不用過多分析,我們重點來看下紅框中類的方法實現。

該方法中最重要的是invokeAwareInterfaces方法,它的作用是檢測對應的bean是否實現了某個Aware介面,如果實現了的話就去進行相關的呼叫。

if (bean instanceof ApplicationEventPublisherAware) {
    ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}

我們發現在invokeAwareInterfaces方法中出現瞭如上程式碼,這不就是和廣播傳送相關的嗎?所以只要我們寫一個類來實現ApplicationEventPublisherAware介面,就可以在該bean中注入一個ApplicationEventPublisher物件,也就獲得了傳送廣播的能力。

監聽訊息

applyBeanPostProcessorsAfterInitialization方法會去遍歷該工廠建立的所有的Bean後置處理器,然後去依次執行後置處理器對應的postProcessAfterInitialization方法。

同樣的,該方法的實現類中也有ApplicationContextAwareProcessorApplicationListenerDetector兩個類,但是不同的是,前者的類的實現方法是預設實現按照原樣返回的給定bean,而後者做了相關的處理。

this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);

上述程式碼是將實現了ApplicationListener介面的bean新增到監聽器列表中,最終是儲存在AbstractApplicationEventMulticaster的成員變數defaultRetriever的集合applicationListeners中。

猜想:當傳送廣播訊息時,就直接找到集合中的這些監聽器,然後呼叫每個監聽器的onApplicationEvent方法完成事件的處理。

案例分析

refresh()finishRefresh()方法中,

publishEvent(new ContextRefreshedEvent(this));

傳送一條事件型別為ContextRefreshedEvent的廣播訊息,用來代表Spring容器初始化結束。通過分析發現,該方法中最主要的就是如下程式碼:

//真正的廣播交給 applicationEventMulticaster 來完成
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

refresh()initApplicationEventMulticaster()applicationEventMulticaster初始化為SimpleApplicationEventMulticaster

在實現類SimpleApplicationEventMulticaster的方法中,會找到已註冊的ApplicationListener列表,然後分別呼叫invokeListener方法(將監聽和事件作為引數傳到方法並執行的過程就是傳送廣播的過程)。

底層呼叫的是listener.onApplicationEvent(event);方法,也就是各個監聽實現類單獨處理廣播訊息的邏輯。

訊息與監聽繫結

看到這兒,你是不是已經發現了:訊息型別和監聽器的繫結發生在廣播過程中。接下來就讓我們去一探究竟

我們看一下multicastEvent()方法中的getApplicationListeners(event, type)方法。

在該方法中,用到了ConcurrentHashMap型別的快取retrieverCache,所以每種型別的事件在廣播的時候會觸發一次繫結操作。它的key由事件的來源和型別確定,它的value中就包含了由事件來源和型別所確定的所有監聽列表。

其中繫結的邏輯就出現在retrieveApplicationListeners方法中,大家可以去原始碼中檢視。

實戰教學

紙上得來終覺淺,絕知此事要躬行。為了更好地理解廣播與監聽的流程,我們當然得用實戰來加以輔佐!

自定義事件

public class MyEvent extends ApplicationContextEvent {
    public MyEvent(ApplicationContext source) {
        super(source);
    }
}

自定義廣播

@Component
public class MyPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

	private ApplicationEventPublisher applicationEventPublisher;

	private ApplicationContext applicationContext;

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.applicationEventPublisher = applicationEventPublisher;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	//傳送廣播訊息
	public void publishEvent(){
		System.out.println("我要開始傳送訊息了。。。");
		MyEvent myEvent = new MyEvent(applicationContext);
		applicationEventPublisher.publishEvent(myEvent);
	}
}

MyPublisher實現了ApplicationEventPublisherAware介面 ,在spring初始化(見上文中的invokeAwareInterfaces)的時候會回撥setApplicationEventPublisher方法,獲取到初始化(新增bean後置處理器ApplicationContextAwareProcessor)時的AbstractApplicationContext,而AbstractApplicationContext又間接實現了ApplicationEventPublisher而獲得傳送能力。真正執行的是 AbstractApplicationContext 類中的 publishEvent 方法。

自定義監聽

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("我監聽到你的訊息了");
    }
}

MyEventListener實現了ApplicationListener介面,在spring初始化(見上文中的addApplicationListener)的時候會新增到applicationListeners中,在執行publishEvent 方法時就會走MyEventListener中的onApplicationEvent方法。

客戶端

@RestController
@RequestMapping("/demo")
public class DemoTest {

    @Autowired
    private MyPublisher myPublisher;

    @RequestMapping("/test")
    public void test() {
        myPublisher.publishEvent();
    }
}

訪問127.0.0.1:8008/demo/test就可以傳送廣播了,傳送與監聽內容如下:

我要開始傳送訊息了。。。
我監聽到你的訊息了

看到這兒,相信你己經完全掌握了廣播與監聽的精髓了,趕快實踐起來吧。阿Q將持續更新java實戰方面的文章,感興趣的可以關注下,也可以來技術群討論問題呦!

相關文章