源碼級別的廣播與監聽實現

来源:https://www.cnblogs.com/aqsaycode/archive/2022/04/07/16112678.html
-Advertisement-
Play Games

原創:微信公眾號 【阿Q說代碼】,歡迎分享,轉載請保留出處。 近期疫情形勢嚴峻,情形不容樂觀,周末也不敢出去浪了,躲在家裡“葛優躺”。閑來無事,又翻了遍Spring的源碼。不翻不知道,一翻嚇一跳,之前翻過的源碼已經吃進了肚子里,再見亦是陌生人。 個人建議:為了以後能快速的撿起某個知識點,最好的方法還 ...


原創:微信公眾號 【阿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實戰方面的文章,感興趣的可以關註下,也可以來技術群討論問題呦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文我們來看下 SpringSecurity + JWT 實現單點登錄操作,本文 2W 字,預計閱讀時間 30 min,文章提供了代碼骨架,建議收藏。 一、什麼是單點登陸 單點登錄(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系 ...
  • 一、字元流的由來 由於使用位元組流操控中文時不是很方便,Java就提供了字元流來進行操控中文 實現原理:位元組流+編碼表 為什麼用位元組流進行複製帶有中文的文本文件時沒有問題? 因為底層操作會自動進行位元組拼接成中文 怎樣識別該位元組是中文呢? 漢字在存儲時,無論是UTF-8還是GBK,第一個位元組都是負數用來 ...
  • 《零基礎學Java》 文件輸入/輸出流 程式運行期間,大部分數據都被存儲在記憶體中,當程式結束或被關閉時,存儲在記憶體中的數據將會消失。如果要永久保存數據,那麼最好的辦法就是把數據保存到磁碟的文件中。為此,Java提供了文件輸入/輸出流,即 FilelnputStream類 與 FilcOutputSr ...
  • 之前刷到一個視頻,老師上課點到用系統點到回答問題,然後就點名結束了。相信很多學校現在也會玩這招吧,今天就用Python給大家做一個點名系統。來吧,展示… 一.準備工作 1.Tkinter Tkinter 是 python 內置的 TK GUI 工具集。TK 是 Tcl 語言的原生 GUI 庫。作為 ...
  • SpringCloud Function SpEL註入 漏洞分析 ...
  • 前言大家拍照的時候會用到全景嗎?在拍一個環境的時候還是會有很多人用全景的吧 ,今天教大家如何用Python拼接全景圖片。 圖像的全景拼接,即“縫合”兩張具有重疊區域的圖來創建一張全景圖。其中用到了電腦視覺和圖像處理技術有:關鍵點特征檢 測、局部不變特征、關鍵特征點匹配、RANSAC(Random ...
  • (Java 演算法的 ACM 模式) 前言 經常在 LeetCode 上用核心代碼模式刷題的小伙伴突然用 ACM 模式可能會適應不過來,把時間花在輸入輸出上很浪費時間,因此本篇筆記對 Java 演算法的 ACM 模式做了個小總結; 除此之外,需要註意一些小細節: 1. 數字讀取到字元串讀取間需要用 in ...
  • 上篇文章對併發的理論基礎進行了回顧,主要是為什麼使用多線程、多線程會引發什麼問題及引發的原因,和怎麼使用Java中的多線程去解決這些問題。 正所謂,知其然知其所以然,這是學習一個知識遵循的原則。 推薦讀者先行查看併發編程的理論知識,以便可以絲滑入戲。 併發編程系列之一併發理論基礎 本篇文章重點在於J ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...