掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
1. 事件的層次傳播
在Spring中,ApplicationContext可以形成一個(gè)層次結(jié)構(gòu),通常由主容器和多個(gè)子容器組成。一個(gè)常見的疑問是:當(dāng)一個(gè)事件在其中一個(gè)容器中發(fā)布時(shí),這個(gè)事件會(huì)如何在這個(gè)層次結(jié)構(gòu)中傳播?
為了探討這個(gè)問題,我們創(chuàng)建了一個(gè)名為HierarchicalEventPropagationEvent的事件類和一個(gè)對應(yīng)的監(jiān)聽器HierarchicalEventPropagationListener。
全部代碼如下:
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
// 事件類
public class HierarchicalEventPropagationEvent extends ApplicationEvent {
private String message;
public HierarchicalEventPropagationEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
相應(yīng)地,為 HierarchicalEventPropagationEvent 定義一個(gè)監(jiān)聽器HierarchicalEventPropagationListener :
package com.example.demo.listener;
import com.example.demo.event.HierarchicalEventPropagationEvent;
import org.springframework.context.ApplicationListener;
// 監(jiān)聽器類
public class HierarchicalEventPropagationListener implements ApplicationListener<HierarchicalEventPropagationEvent> {
private String listenerId;
public HierarchicalEventPropagationListener(String listenerId) {
this.listenerId = listenerId;
}
@Override
public void onApplicationEvent(HierarchicalEventPropagationEvent event) {
System.out.println(listenerId + " received event - " + event.getMessage());
}
}
為了測試?yán)^承機(jī)制,我們需要構(gòu)建主容器和子容器,并為每個(gè)容器注冊了一個(gè)監(jiān)聽器。初始化容器后,我們在兩個(gè)容器中分別發(fā)布事件。
請注意,首先需要刷新主容器,然后刷新子容器。否則會(huì)出現(xiàn)異常:Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@somehashcode
主程序如下:
ackage com.example.demo;
import com.example.demo.event.HierarchicalEventPropagationEvent;
import com.example.demo.listener.HierarchicalEventPropagationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
// 創(chuàng)建父容器,注冊監(jiān)聽器
AnnotationConfigApplicationContext parentCtx = new AnnotationConfigApplicationContext();
parentCtx.addApplicationListener(new HierarchicalEventPropagationListener("Parent Listener"));
parentCtx.refresh();
// 創(chuàng)建子容器,注冊監(jiān)聽器
AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
childCtx.setParent(parentCtx);
childCtx.addApplicationListener(new HierarchicalEventPropagationListener("Child Listener"));
childCtx.refresh();
// 發(fā)布事件
HierarchicalEventPropagationEvent event1 = new HierarchicalEventPropagationEvent(parentCtx, "Event from parent");
parentCtx.publishEvent(event1);
HierarchicalEventPropagationEvent event2 = new HierarchicalEventPropagationEvent(childCtx, "Event from child");
childCtx.publishEvent(event2);
}
}
運(yùn)行結(jié)果
主容器發(fā)布的事件只觸發(fā)了一次監(jiān)聽,而子容器發(fā)布的事件觸發(fā)了兩次監(jiān)聽。父容器和子容器都監(jiān)聽到了來自子容器的事件,而只有父容器監(jiān)聽到了來自父容器的事件。
所以得出結(jié)論:在Spring的父子容器結(jié)構(gòu)中,事件會(huì)從子容器向上傳播至其父容器,但父容器中發(fā)布的事件不會(huì)向下傳播至子容器。 這種設(shè)計(jì)可以幫助開發(fā)者在父容器中集中處理所有的事件,而不必?fù)?dān)心事件在多個(gè)子容器之間的傳播。
2. PayloadApplicationEvent的使用
PayloadApplicationEvent是Spring提供的一種特殊事件,用于傳遞數(shù)據(jù)(稱為"payload")。所以不需要自定義事件,PayloadApplicationEvent可以直接傳遞任何類型的數(shù)據(jù),只需要指定它的類型即可。
全部代碼如下:
- 定義監(jiān)聽器
首先,我們來看怎樣定義一個(gè)監(jiān)聽器來接收這個(gè)事件:
通用監(jiān)聽器 - 會(huì)監(jiān)聽到所有種類的PayloadApplicationEvent:
package com.example.demo.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
/**
* 通用監(jiān)聽器,能監(jiān)聽到所有類型的PayloadApplicationEvent
*/
public class CustomObjectApplicationListener implements ApplicationListener<PayloadApplicationEvent> {
@Override
public void onApplicationEvent(PayloadApplicationEvent event) {
System.out.println("收到PayloadApplicationEvent,數(shù)據(jù)是:" + event.getPayload());
}
}
特定數(shù)據(jù)類型的監(jiān)聽器 - 只會(huì)監(jiān)聽指定類型的數(shù)據(jù)。例如,如果我們只對字符串?dāng)?shù)據(jù)感興趣,我們可以如此定義:
package com.example.demo.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
/**
* 特定數(shù)據(jù)類型的監(jiān)聽器。這個(gè)監(jiān)聽器專門監(jiān)聽String類型的PayloadApplicationEvent
*/
public class CustomStringApplicationListener implements ApplicationListener<PayloadApplicationEvent<String>> {
@Override
public void onApplicationEvent(PayloadApplicationEvent<String> event) {
System.out.println("收到了字符串?dāng)?shù)據(jù):" + event.getPayload());
}
}
- 測試示例
要看這兩種監(jiān)聽器如何工作,我們來寫一個(gè)測試。
package com.example.demo;
import com.example.demo.listener.CustomObjectApplicationListener;
import com.example.demo.listener.CustomStringApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Date;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// 注冊監(jiān)聽器
ctx.addApplicationListener(new CustomObjectApplicationListener());
ctx.addApplicationListener(new CustomStringApplicationListener());
ctx.refresh();
// 發(fā)送事件
ctx.publishEvent("Hello, World!"); // 發(fā)送一個(gè)字符串
ctx.publishEvent(2023); // 發(fā)送一個(gè)整數(shù)
ctx.publishEvent(new Date()); // 發(fā)送一個(gè)日期對象
}
}
在這個(gè)測試中,我們發(fā)送了三種類型的數(shù)據(jù):一個(gè)字符串、一個(gè)整數(shù)和一個(gè)日期。
執(zhí)行結(jié)果如下:
從輸出可以看出:
第一種監(jiān)聽器(通用的)接收到了所有三個(gè)事件,因?yàn)樗魂P(guān)心數(shù)據(jù)的具體類型。
第二種監(jiān)聽器(字符串專用的)只接收到了字符串類型的事件。
3. 為什么選擇自定義事件?
雖然PayloadApplicationEvent提供了簡化事件監(jiān)聽的能力,但其可能不足以滿足特定的業(yè)務(wù)需求,尤其是當(dāng)需要更多上下文和數(shù)據(jù)時(shí)。下面是一個(gè)使用自定義事件ArticlePublishedEvent的例子。
全部代碼如下:
自定義事件: ArticlePublishedEvent
這個(gè)事件代表了“新文章發(fā)布”,附帶有文章的標(biāo)題、作者和發(fā)布日期等信息。
package com.example.demo.event;
import org.springframework.context.ApplicationEvent;
public class ArticlePublishedEvent extends ApplicationEvent {
private String title;
private String author;
private String publishedDate;
public ArticlePublishedEvent(Object source, String title, String author, String publishedDate) {
super(source);
this.title = title;
this.author = author;
this.publishedDate = publishedDate;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public String getPublishedDate() {
return publishedDate;
}
}
自定義監(jiān)聽器: ArticlePublishedListener
這個(gè)監(jiān)聽器專門響應(yīng)ArticlePublishedEvent,執(zhí)行特定的業(yè)務(wù)邏輯,例如通知訂閱者、更新搜索索引等。
import org.springframework.context.ApplicationListener;
public class ArticlePublishedListener implements ApplicationListener<ArticlePublishedEvent> {
@Override
public void onApplicationEvent(ArticlePublishedEvent event) {
System.out.println("A new article has been published!");
System.out.println("Title: " + event.getTitle());
System.out.println("Author: " + event.getAuthor());
System.out.println("Published Date: " + event.getPublishedDate());
// Notify subscribers about the new article
notifySubscribers(event);
// Update search engine index with new article details
updateSearchIndex(event);
// Update statistical data about articles
updateStatistics(event);
}
private void notifySubscribers(ArticlePublishedEvent event) {
// Logic to notify subscribers (dummy logic for demonstration)
System.out.println("Notifying subscribers about the new article titled: " + event.getTitle());
}
private void updateSearchIndex(ArticlePublishedEvent event) {
// Logic to update search engine index (dummy logic for demonstration)
System.out.println("Updating search index with the new article titled: " + event.getTitle());
}
private void updateStatistics(ArticlePublishedEvent event) {
// Logic to update statistical data (dummy logic for demonstration)
System.out.println("Updating statistics with the new article titled: " + event.getTitle());
}
}
-----------------------------------
?著作權(quán)歸作者所有:來自51CTO博客作者華為云開發(fā)者聯(lián)盟的原創(chuàng)作品,請聯(lián)系作者獲取轉(zhuǎn)載授權(quán),否則將追究法律責(zé)任
掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
https://blog.51cto.com/u_15214399/8112689
在接收到新文章發(fā)布的事件后,監(jiān)聽器ArticlePublishedListener需要執(zhí)行以下業(yè)務(wù)邏輯:
- 通知所有訂閱新文章通知的用戶。
- 將新文章的標(biāo)題、作者和發(fā)布日期添加到搜索引擎的索引中,以便用戶可以搜索到這篇新文章。
- 更新統(tǒng)計(jì)信息,例如總文章數(shù)、最近發(fā)布的文章等。
這樣,每次新文章發(fā)布的事件被觸發(fā)時(shí),訂閱者都會(huì)被通知,搜索引擎的索引將會(huì)得到更新,同時(shí)相關(guān)的統(tǒng)計(jì)數(shù)據(jù)也會(huì)得到更新。
主程序:
模擬文章發(fā)布的場景
package com.example.demo;
import com.example.demo.event.ArticlePublishedEvent;
import com.example.demo.listener.ArticlePublishedListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
// Initialize Spring ApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// Register listener
ctx.addApplicationListener(new ArticlePublishedListener());
ctx.refresh();
// Simulate publishing an article
ctx.publishEvent(new ArticlePublishedEvent(ctx, "Spring Events", "John Doe", "2023-09-15"));
}
}
-----------------------------------
?著作權(quán)歸作者所有:來自51CTO博客作者華為云開發(fā)者聯(lián)盟的原創(chuàng)作品,請聯(lián)系作者獲取轉(zhuǎn)載授權(quán),否則將追究法律責(zé)任
掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
https://blog.51cto.com/u_15214399/8112689
運(yùn)行結(jié)果如下:
我們可以看到ArticlePublishedEvent比PayloadApplicationEvent具有更多的業(yè)務(wù)含義和上下文。這樣的設(shè)計(jì)使我們能夠更具體地響應(yīng)和處理特定的業(yè)務(wù)事件。
實(shí)際上,在企業(yè)級應(yīng)用中,文章發(fā)布可能會(huì)觸發(fā)多種不同的后續(xù)動(dòng)作,使用Spring的事件監(jiān)聽器模式可以帶來如下優(yōu)勢:
解耦:事件發(fā)布者(即新文章發(fā)布功能)不必關(guān)心具體的后續(xù)處理步驟。它只需發(fā)布事件,然后其他感興趣的監(jiān)聽器會(huì)相應(yīng)地做出響應(yīng)。這種設(shè)計(jì)有助于各個(gè)功能之間的解耦。
可擴(kuò)展性:如果未來需要為新文章發(fā)布添加更多的后續(xù)處理,只需添加更多的監(jiān)聽器即可,無需修改原有的業(yè)務(wù)邏輯。
維護(hù)性:由于功能之間的解耦,每個(gè)功能模塊都可以獨(dú)立維護(hù),這有助于提高代碼的可維護(hù)性。
Spring為開發(fā)者提供了強(qiáng)大的事件監(jiān)聽機(jī)制,無論是使用自定義事件還是利用PayloadApplicationEvent進(jìn)行快速開發(fā),都使我們能夠構(gòu)建一個(gè)高度解耦、可擴(kuò)展且易于維護(hù)的系統(tǒng)。
4. 事件廣播原理
4.1 Spring 5.x的事件模型概述
核心概念:
ApplicationEvent:這是所有Spring事件的超類。用戶可以通過繼承此類來創(chuàng)建自定義事件。
ApplicationListener:這是所有事件監(jiān)聽器的接口。它定義了一個(gè)onApplicationEvent方法,用于處理特定類型的事件。
ApplicationEventPublisher:這是一個(gè)接口,定義了發(fā)布事件的方法。ApplicationContext繼承了這個(gè)接口,因此任何Spring bean都可以發(fā)布事件。
ApplicationEventMulticaster:這個(gè)組件負(fù)責(zé)將事件廣播到所有匹配的監(jiān)聽器。
事件發(fā)布:
用戶可以通過ApplicationEventPublisher接口或ApplicationContext來發(fā)布事件。通常情況下,當(dāng)我們在Spring bean中需要發(fā)布事件時(shí),可以讓這個(gè)bean實(shí)現(xiàn)ApplicationEventPublisherAware接口,這樣Spring容器會(huì)注入一個(gè)事件發(fā)布器。
異步事件:
從Spring 4.2開始,我們可以輕松地使事件監(jiān)聽器異步化。在Spring 5中,這一功能仍然得到支持。只需要在監(jiān)聽器方法上添加@Async注解并確保啟用了異步支持。這使得事件處理可以在單獨(dú)的線程中執(zhí)行,不阻塞發(fā)布者。
泛型事件:
Spring 4.2引入了對泛型事件的支持,這在Spring 5中得到了維護(hù)。這意味著監(jiān)聽器現(xiàn)在可以根據(jù)事件的泛型類型進(jìn)行過濾。例如,一個(gè)ApplicationListener<ApplicationEvent<String>>將只接收到攜帶String負(fù)載的事件。
事件的排序:
監(jiān)聽器可以實(shí)現(xiàn)Ordered接口或使用@Order注解來指定事件的執(zhí)行順序。
新的事件類型:
Spring 5引入了新的事件類型,如ServletRequestHandledEvent,為web請求處理提供更多的鉤子。而像ContextRefreshedEvent這樣的事件,雖然不是Spring 5新引入的,但它為特定的生命周期回調(diào)提供了鉤子。
Reactive事件模型:
與Spring 5引入的WebFlux一起,還引入了對反應(yīng)式編程模型的事件監(jiān)聽和發(fā)布的支持。
總結(jié):
在Spring 5.x中,事件模型得到了進(jìn)一步的增強(qiáng)和優(yōu)化,增加了對異步、泛型和反應(yīng)式編程的支持,提供了更強(qiáng)大、靈活和高效的機(jī)制來處理應(yīng)用程序事件。對于開發(fā)者來說,這為在解耦的同時(shí)實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)邏輯提供了便利。
4.2 發(fā)布事件publishEvent源碼分析
上圖,這里是Spring 5.3.7的源碼,下面講單獨(dú)抽出來分析
public void publishEvent(ApplicationEvent event) {
this.publishEvent(event, (ResolvableType)null);
}
分析:
該方法接受一個(gè)ApplicationEvent對象并調(diào)用其重載版本publishEvent(Object event, @Nullable ResolvableType eventType),為其傳遞null作為事件類型。這是為了簡化用戶使用,用戶可以直接傳遞一個(gè)ApplicationEvent對象而無需考慮其具體的類型。
public void publishEvent(Object event) {
this.publishEvent(event, (ResolvableType)null);
}
分析:
與上一個(gè)方法類似,但它接受任何Object作為事件,并將其與null的eventType一起傳遞給核心方法。這增加了靈活性,用戶可以發(fā)送任何對象作為事件,而不僅僅是ApplicationEvent對象。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// 檢查事件對象是否為空,確保發(fā)布的事件是有意義的
Assert.notNull(event, "Event must not be null");
// 判斷傳入的事件是否已經(jīng)是ApplicationEvent類型,如果是,則無需再進(jìn)行包裝
Object applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
// 如果傳入的事件不是ApplicationEvent類型,則將其包裝為PayloadApplicationEvent
applicationEvent = new PayloadApplicationEvent(this, event);
// 如果未指定事件類型,那么從包裝后的事件中獲取其真實(shí)類型
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
// 判斷當(dāng)前是否存在earlyApplicationEvents列表
if (this.earlyApplicationEvents != null) {
// 如果存在,說明ApplicationContext還未完全初始化,將事件添加到此列表中,稍后再進(jìn)行處理
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 如果ApplicationContext已經(jīng)初始化,那么直接通過事件多播器廣播事件
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
// 如果存在父ApplicationContext,則也將事件發(fā)布到父容器中
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
// 如果父容器是AbstractApplicationContext類型,則帶上事件類型進(jìn)行發(fā)布
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
// 否則,只傳遞事件對象進(jìn)行發(fā)布
this.parent.publishEvent(event);
}
}
}
-----------------------------------
?著作權(quán)歸作者所有:來自51CTO博客作者華為云開發(fā)者聯(lián)盟的原創(chuàng)作品,請聯(lián)系作者獲取轉(zhuǎn)載授權(quán),否則將追究法律責(zé)任
掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
https://blog.51cto.com/u_15214399/8112689
這個(gè)方法究竟做了什么?
事件非空檢查:為了確保事件對象不為空,進(jìn)行了初步的斷言檢查。這是一個(gè)常見的做法,以防止無效的事件被廣播。
事件類型檢查與封裝:Spring允許使用任意類型的對象作為事件。如果傳入的不是ApplicationEvent的實(shí)例,它會(huì)使用PayloadApplicationEvent來進(jìn)行封裝。這種設(shè)計(jì)提供了更大的靈活性。
早期事件的處理:在Spring的生命周期中,ApplicationContext可能還沒有完全初始化,這時(shí)會(huì)有一些早期的事件。如果earlyApplicationEvents不為空,這些事件會(huì)被添加到此列表中,稍后再廣播。
事件廣播:如果ApplicationContext已初始化,事件會(huì)被廣播給所有的監(jiān)聽器。這是通過ApplicationEventMulticaster完成的,它是Spring中負(fù)責(zé)事件廣播的核心組件。
處理父ApplicationContext:在有些應(yīng)用中,可以存在父子ApplicationContext。當(dāng)子容器廣播一個(gè)事件時(shí),也可以考慮在父容器中廣播這個(gè)事件。這是為了確保在整個(gè)上下文層次結(jié)構(gòu)中的所有感興趣的監(jiān)聽器都能收到事件。
通過這種方式,Spring的事件發(fā)布機(jī)制確保了事件在不同的上下文和生命周期階段都能被正確處理和廣播。
上面說到事件廣播是ApplicationEventMulticaster完成的,這個(gè)是什么?下面來看看
4.3 Spring事件廣播:從ApplicationEventMulticaster開始
當(dāng)我們在Spring中討論事件,我們實(shí)際上是在討論兩件事:事件(即發(fā)生的事情)和監(jiān)聽器(即對這些事件感興趣并作出反應(yīng)的實(shí)體)。
ApplicationEventMulticaster的主要職責(zé)是管理事件監(jiān)聽器并廣播事件給這些監(jiān)聽器。我們主要關(guān)注SimpleApplicationEventMulticaster,因?yàn)檫@是默認(rèn)的實(shí)現(xiàn),但請注意,Spring允許替換為自定義的ApplicationEventMulticaster。
以下是SimpleApplicationEventMulticaster中的相關(guān)代碼與分析,有興趣的小伙伴可以自行查看:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
// 可選的任務(wù)執(zhí)行器,用于異步調(diào)用事件監(jiān)聽器。
@Nullable
private Executor taskExecutor;
// 可選的錯(cuò)誤處理器,用于處理在廣播事件過程中出現(xiàn)的錯(cuò)誤。
@Nullable
private ErrorHandler errorHandler;
// 用于記錄日志的logger,它是延遲初始化的。
@Nullable
private volatile Log lazyLogger;
// 默認(rèn)構(gòu)造函數(shù)。
public SimpleApplicationEventMulticaster() {
}
// 帶有BeanFactory參數(shù)的構(gòu)造函數(shù),通常用于更復(fù)雜的應(yīng)用上下文配置中。
public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
this.setBeanFactory(beanFactory);
}
// 設(shè)置任務(wù)執(zhí)行器??梢允侨魏蜫ava Executor,比如Spring的SimpleAsyncTaskExecutor或Java的FixedThreadPool。
public void setTaskExecutor(@Nullable Executor taskExecutor) {
this.taskExecutor = taskExecutor;
}
// 獲取當(dāng)前設(shè)置的任務(wù)執(zhí)行器。
@Nullable
protected Executor getTaskExecutor() {
return this.taskExecutor;
}
// 設(shè)置錯(cuò)誤處理器。
public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
// 獲取當(dāng)前設(shè)置的錯(cuò)誤處理器。
@Nullable
protected ErrorHandler getErrorHandler() {
return this.errorHandler;
}
// 這是廣播事件的主要方法。它首先解析事件的類型,然后調(diào)用具有額外參數(shù)的重載方法。
public void multicastEvent(ApplicationEvent event) {
this.multicastEvent(event, this.resolveDefaultEventType(event));
}
// 這個(gè)方法是真正執(zhí)行廣播操作的方法。
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
// 確定事件類型。
ResolvableType type = (eventType != null) ? eventType : this.resolveDefaultEventType(event);
// 獲取任務(wù)執(zhí)行器。
Executor executor = this.getTaskExecutor();
// 獲取匹配此事件類型的所有監(jiān)聽器。
Iterator<ApplicationListener<?>> listeners = this.getApplicationListeners(event, type).iterator();
// 遍歷每個(gè)監(jiān)聽器并調(diào)用它。
while(listeners.hasNext()) {
ApplicationListener<?> listener = listeners.next();
// 如果有設(shè)置任務(wù)執(zhí)行器,則異步調(diào)用監(jiān)聽器。
if (executor != null) {
executor.execute(() -> this.invokeListener(listener, event));
} else {
// 如果沒有設(shè)置任務(wù)執(zhí)行器,則同步調(diào)用監(jiān)聽器。
this.invokeListener(listener, event);
}
}
}
// 為給定的事件解析默認(rèn)的事件類型。
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
return ResolvableType.forInstance(event);
}
// 調(diào)用指定的監(jiān)聽器來處理給定的事件,并根據(jù)需要處理錯(cuò)誤。
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 獲取當(dāng)前的錯(cuò)誤處理器。
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
// 嘗試調(diào)用監(jiān)聽器。
this.doInvokeListener(listener, event);
} catch (Throwable ex) {
// 如果出現(xiàn)錯(cuò)誤,使用錯(cuò)誤處理器處理。
errorHandler.handleError(ex);
}
} else {
// 如果沒有設(shè)置錯(cuò)誤處理器,則直接調(diào)用監(jiān)聽器。
this.doInvokeListener(listener, event);
}
}
// 直接調(diào)用監(jiān)聽器,捕獲任何類型不匹配的異常。
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (ClassCastException ex) {
// 捕獲類型轉(zhuǎn)換異常,并根據(jù)需要進(jìn)行處理。
// 這可以確保如果監(jiān)聽器不能處理特定類型的事件,不會(huì)導(dǎo)致整個(gè)廣播操作失敗。
String msg = ex.getMessage();
if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
throw ex;
}
// 在預(yù)期情況下捕獲并記錄異常,而不是拋出它。
Log loggerToUse = this.lazyLogger;
if (loggerToUse == null) {
loggerToUse = LogFactory.getLog(this.getClass());
this.lazyLogger = loggerToUse;
}
if (loggerToUse.isTraceEnabled()) {
loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
}
}
}
// 根據(jù)給定的類型錯(cuò)誤消息和事件類來檢查ClassCastException是否是預(yù)期的。
private boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) {
if (classCastMessage.startsWith(eventClass.getName())) {
return true;
} else if (classCastMessage.startsWith(eventClass.toString())) {
return true;
} else {
int moduleSeparatorIndex = classCastMessage.indexOf(47);
return moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1);
}
}
}
關(guān)于核心方法multicastEvent需要作出特別說明:
// 這個(gè)方法是真正執(zhí)行廣播操作的方法。
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
// 確定事件類型。
ResolvableType type = (eventType != null) ? eventType : this.resolveDefaultEventType(event);
......
// 獲取匹配此事件類型的所有監(jiān)聽器。
Iterator<ApplicationListener<?>> listeners = this.getApplicationListeners(event, type).iterator();
......
}
-----------------------------------
?著作權(quán)歸作者所有:來自51CTO博客作者華為云開發(fā)者聯(lián)盟的原創(chuàng)作品,請聯(lián)系作者獲取轉(zhuǎn)載授權(quán),否則將追究法律責(zé)任
掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
https://blog.51cto.com/u_15214399/8112689
方法第一行得到一個(gè)ResolvableType類型的對象,為什么 Spring 選擇使用 ResolvableType 而不是直接使用 Java 類型?最主要的原因是 Java 的泛型擦除。 在 Java 中,泛型只存在于編譯時(shí),一旦代碼被編譯,泛型信息就會(huì)被擦除,運(yùn)行時(shí)就不能直接獲取到泛型的實(shí)際類型。
為了解決這個(gè)問題,Spring 引入了 ResolvableType,一個(gè)能夠解析泛型類型信息的工具類。
舉個(gè)例子:
假設(shè)有如下的類定義:
public class Sample {
private List<String> names;
}
我們可以這樣獲取 names 字段的泛型類型:
ResolvableType t = ResolvableType.forField(Sample.class.getDeclaredField("names"));
Class<?> genericType = t.getGeneric(0).resolve(); // 得到 String.class
在 Spring 事件中的使用
ResolvableType 在 Spring 事件中的應(yīng)用主要是確定事件的類型和監(jiān)聽器監(jiān)聽的事件類型。當(dāng)我們發(fā)布一個(gè)事件:
ApplicationEvent event = new MyCustomEvent(this, "data");
applicationContext.publishEvent(event);
Spring 內(nèi)部會(huì)使用 ResolvableType.forInstance(event) 來獲取這個(gè)事件的類型。然后,它會(huì)找到所有注冊的監(jiān)聽器,查看它們監(jiān)聽的事件類型是否與此事件匹配(通過比較 ResolvableType)。匹配的監(jiān)聽器會(huì)被調(diào)用。
對于一個(gè)監(jiān)聽器:
public class MyListener implements ApplicationListener<MyCustomEvent> {
@Override
public void onApplicationEvent(MyCustomEvent event) {
// handle the event
}
}
Spring 內(nèi)部會(huì)使用 ResolvableType 來解析這個(gè)監(jiān)聽器監(jiān)聽的事件類型(在這個(gè)例子中是 MyCustomEvent)。
總之,ResolvableType 在 Spring 中的主要用途是提供了一種方式來解析和操作運(yùn)行時(shí)的泛型類型信息,特別是在事件發(fā)布和監(jiān)聽中。
4.4 Spring事件發(fā)布與處理流程圖
如果看不清,建議在新標(biāo)簽頁中打開圖片后放大看
4.5 監(jiān)聽器內(nèi)部邏輯
再來看看監(jiān)聽器內(nèi)部邏輯,我們來分析在multicastEvent方法中調(diào)用的getApplicationListeners(event, type)來分析下
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
// 獲取事件來源對象
Object source = event.getSource();
// 判斷事件來源對象是否為null,是則返回null,否則返回事件來源對象的類
Class<?> sourceType = source != null ? source.getClass() : null;
// 使用事件類型和源類型作為緩存鍵
AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
// 初始化一個(gè)新的監(jiān)聽器檢索器為null
AbstractApplicationEventMulticaster.CachedListenerRetriever newRetriever = null;
// 嘗試從緩存中使用鍵取得一個(gè)已存在的檢索器
AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);
// 如果沒有從緩存中獲取到檢索器,并且滿足緩存安全性條件
if (existingRetriever == null && (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
// 創(chuàng)建一個(gè)新的檢索器
newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();
// 嘗試將新檢索器添加到緩存中
existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
// 如果緩存中已經(jīng)有了一個(gè)值(由于并發(fā)的原因),則將新檢索器設(shè)回null
if (existingRetriever != null) {
newRetriever = null;
}
}
// 如果有現(xiàn)有的檢索器
if (existingRetriever != null) {
// 嘗試從檢索器中獲取監(jiān)聽器集合
Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
// 如果結(jié)果不為null,則直接返回
if (result != null) {
return result;
}
}
// 如果上述步驟都沒有返回,調(diào)用retrieveApplicationListeners進(jìn)行實(shí)際的監(jiān)聽器檢索
return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);
}
private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.CachedListenerRetriever retriever) {
// 初始化一個(gè)空的監(jiān)聽器列表
List<ApplicationListener<?>> allListeners = new ArrayList();
// 若retriever非null,則初始化集合來保存過濾出來的監(jiān)聽器和Bean名
Set<ApplicationListener<?>> filteredListeners = retriever != null ? new LinkedHashSet() : null;
Set<String> filteredListenerBeans = retriever != null ? new LinkedHashSet() : null;
LinkedHashSet listeners;
LinkedHashSet listenerBeans;
// 同步從defaultRetriever中獲取已注冊的監(jiān)聽器和其Bean名稱
synchronized(this.defaultRetriever) {
listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
}
// 遍歷所有的監(jiān)聽器
for (ApplicationListener<?> listener : listeners) {
// 檢查該監(jiān)聽器是否支持此事件類型和源類型
if (this.supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
// 如果支持并且retriever非null,添加到過濾監(jiān)聽器集合
filteredListeners.add(listener);
}
// 將支持的監(jiān)聽器添加到allListeners列表
allListeners.add(listener);
}
}
// 如果存在監(jiān)聽器Bean名稱
if (!listenerBeans.isEmpty()) {
ConfigurableBeanFactory beanFactory = this.getBeanFactory();
for (String listenerBeanName : listenerBeans) {
try {
// 檢查Bean工廠中的Bean是否支持該事件
if (this.supportsEvent(beanFactory, listenerBeanName, eventType)) {
ApplicationListener<?> listener = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
// 再次檢查確保Bean實(shí)例支持事件,并且它還沒有被加入allListeners列表
if (!allListeners.contains(listener) && this.supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
// 若該Bean是單例并且retriever非null,添加到過濾監(jiān)聽器集合
if (beanFactory.isSingleton(listenerBeanName)) {
filteredListeners.add(listener);
} else {
filteredListenerBeans.add(listenerBeanName);
}
}
// 添加到allListeners列表
allListeners.add(listener);
}
} else {
// 若不支持該事件,從allListeners中移除該Bean
Object listener = beanFactory.getSingleton(listenerBeanName);
if (retriever != null) {
filteredListeners.remove(listener);
}
allListeners.remove(listener);
}
} catch (NoSuchBeanDefinitionException e) {
// 若Bean不存在,直接繼續(xù)下一個(gè)
}
}
}
// 對allListeners列表進(jìn)行排序,確保監(jiān)聽器的執(zhí)行順序
AnnotationAwareOrderComparator.sort(allListeners);
// 如果retriever非null,更新其內(nèi)部集合以后續(xù)使用
if (retriever != null) {
if (filteredListenerBeans.isEmpty()) {
retriever.applicationListeners = new LinkedHashSet(allListeners);
retriever.applicationListenerBeans = filteredListenerBeans;
} else {
retriever.applicationListeners = filteredListeners;
retriever.applicationListenerBeans = filteredListenerBeans;
}
}
// 返回allListeners作為結(jié)果
return allListeners;
}
-----------------------------------
?著作權(quán)歸作者所有:來自51CTO博客作者華為云開發(fā)者聯(lián)盟的原創(chuàng)作品,請聯(lián)系作者獲取轉(zhuǎn)載授權(quán),否則將追究法律責(zé)任
掌握Spring事件監(jiān)聽器的內(nèi)部邏輯與實(shí)現(xiàn)
https://blog.51cto.com/u_15214399/8112689
監(jiān)聽器內(nèi)部做了什么?
在getApplicationListeners方法中,采用了一種優(yōu)化檢索的緩存機(jī)制來提高性能并確保線程安全性。
具體分析如下:
首次檢查:
AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever =
(AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);
這里,我們首先從retrieverCache中檢索existingRetriever。
判斷是否需要進(jìn)入同步代碼塊:
if (existingRetriever == null && (this.beanClassLoader == null || ...)) {
...
}
如果existingRetriever為空,那么我們可能需要?jiǎng)?chuàng)建一個(gè)新的CachedListenerRetriever并放入緩存中。但是,為了確保線程安全性,我們必須在這之前進(jìn)行進(jìn)一步的檢查。
雙重檢查:
在創(chuàng)建新的CachedListenerRetriever之前,我們使用了putIfAbsent方法。這個(gè)方法會(huì)嘗試添加一個(gè)新值,但如果該值已存在,它只會(huì)返回現(xiàn)有的值。該機(jī)制采用了一種緩存優(yōu)化策略:通過ConcurrentMap的putIfAbsent方法,即使多個(gè)線程同時(shí)到達(dá)這個(gè)代碼段,也確保只有一個(gè)線程能夠成功地放入新的值,從而保證線程安全性。
newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();
existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
這里的邏輯使用了ConcurrentMap的putIfAbsent方法來確保線程安全性,而沒有使用傳統(tǒng)的synchronized塊。
所以,我們可以說getApplicationListeners中的這部分邏輯采用了一種優(yōu)化檢索的緩存機(jī)制。它利用了并發(fā)容器的原子性操作putIfAbsent來保證線程安全,而不是依賴于傳統(tǒng)的雙重檢查鎖定模式。
總體概括一下,對于getApplicationListeners和retrieveApplicationListeners兩個(gè)方法的功能可以總結(jié)為以下三個(gè)步驟:
從默認(rèn)檢索器篩選監(jiān)聽器:
這部分代碼直接從defaultRetriever中獲取監(jiān)聽器,并檢查它們是否支持當(dāng)前事件。在retrieveApplicationListeners方法中,代碼首先從defaultRetriever中獲取已經(jīng)編程式注入的監(jiān)聽器,并檢查每個(gè)監(jiān)聽器是否支持當(dāng)前的事件類型。
listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
for (ApplicationListener<?> listener : listeners) {
if (this.supportsEvent(listener, eventType, sourceType)) {
... // 添加到篩選出來的監(jiān)聽器列表
}
}
從IOC容器中篩選監(jiān)聽器:
在retrieveApplicationListeners方法中,除了從defaultRetriever中獲取已經(jīng)編程式注入的監(jiān)聽器,代碼還會(huì)嘗試從IOC容器(通過bean名稱)獲取監(jiān)聽器,并檢查它們是否支持當(dāng)前的事件。
if (!listenerBeans.isEmpty()) {
ConfigurableBeanFactory beanFactory = this.getBeanFactory();
for (String listenerBeanName : listenerBeans) {
... // 檢查并添加到篩選出來的監(jiān)聽器列表
}
}
監(jiān)聽器排序:
最后,為確保監(jiān)聽器按照預(yù)定的順序響應(yīng)事件,篩選出的所有監(jiān)聽器會(huì)經(jīng)過排序。排序基于Spring的@Order注解或Ordered接口,如AnnotationAwareOrderComparator.sort(allListeners)所示
AnnotationAwareOrderComparator.sort(allListeners);
4.6 Spring事件監(jiān)聽器檢索流程圖
5. Spring事件傳播、異步處理等機(jī)制的詳細(xì)圖示
說明:
容器和事件廣播:
ApplicationContext 是Spring的應(yīng)用上下文容器。在圖中,我們有一個(gè)主容器和一個(gè)子容器。
當(dāng)我們想發(fā)布一個(gè)事件時(shí),我們調(diào)用 publishEvent 方法。
ApplicationEventMulticaster 負(fù)責(zé)實(shí)際地將事件廣播到各個(gè)監(jiān)聽器。
主容器和子容器關(guān)系:
在Spring中,可以有多個(gè)容器,其中一個(gè)是主容器,其他的則是子容器。
通常,子容器可以訪問主容器中的bean,但反之則不行。但在事件傳播的上下文中,子容器發(fā)布的事件默認(rèn)不會(huì)在主容器中傳播。這一點(diǎn)由 Note1 注釋標(biāo)明。
異步處理:
當(dāng)事件被發(fā)布時(shí),它可以被異步地傳播到監(jiān)聽器,這取決于是否配置了異步執(zhí)行器。
是否使用異步執(zhí)行器? 這個(gè)決策點(diǎn)說明了基于配置,事件可以同步或異步地傳播到監(jiān)聽器。
事件生命周期:
在Spring容器的生命周期中,有些事件在容器初始化前觸發(fā),這些被稱為 early events。這些事件會(huì)被緩存起來,直到容器初始化完成。
一旦容器初始化完成,這些早期的事件會(huì)被處理,并開始處理常規(guī)事件。
在容器銷毀時(shí),也可能觸發(fā)事件。
注意事項(xiàng) (Note1):
這個(gè)部分強(qiáng)調(diào)了一個(gè)特定的行為,即在某些配置下,子容器發(fā)布的事件可能也會(huì)在主容器中傳播,但這并不是默認(rèn)行為。