国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Spring高手之路15——掌握Spring事件監(jiān)聽器的內部邏輯與實現(xiàn)

這篇具有很好參考價值的文章主要介紹了Spring高手之路15——掌握Spring事件監(jiān)聽器的內部邏輯與實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

在閱讀本文之前需要你已經(jīng)對事件監(jiān)聽器有了簡單的了解,或去閱讀前面的文章《Spring高手之路7——事件機制與監(jiān)聽器的全面探索

1. 事件的層次傳播

??在Spring中,ApplicationContext可以形成一個層次結構,通常由主容器和多個子容器組成。一個常見的疑問是:當一個事件在其中一個容器中發(fā)布時,這個事件會如何在這個層次結構中傳播?

??為了探討這個問題,我們創(chuàng)建了一個名為HierarchicalEventPropagationEvent的事件類和一個對應的監(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;
    }
}

相應地,為 HierarchicalEventPropagationEvent 定義一個監(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());
    }

}

??為了測試繼承機制,我們需要構建主容器和子容器,并為每個容器注冊了一個監(jiān)聽器。初始化容器后,我們在兩個容器中分別發(fā)布事件。

??請注意,首先需要刷新主容器,然后刷新子容器。否則會出現(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

主程序如下:

package 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);
    }
}

運行結果

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

??主容器發(fā)布的事件只觸發(fā)了一次監(jiān)聽,而子容器發(fā)布的事件觸發(fā)了兩次監(jiān)聽。父容器和子容器都監(jiān)聽到了來自子容器的事件,而只有父容器監(jiān)聽到了來自父容器的事件。

??所以得出結論:Spring的父子容器結構中,事件會從子容器向上傳播至其父容器,但父容器中發(fā)布的事件不會向下傳播至子容器。 這種設計可以幫助開發(fā)者在父容器中集中處理所有的事件,而不必擔心事件在多個子容器之間的傳播。

2. PayloadApplicationEvent的使用

??PayloadApplicationEventSpring提供的一種特殊事件,用于傳遞數(shù)據(jù)(稱為"payload")。所以不需要自定義事件,PayloadApplicationEvent可以直接傳遞任何類型的數(shù)據(jù),只需要指定它的類型即可。

全部代碼如下:

  • 定義監(jiān)聽器

首先,我們來看怎樣定義一個監(jiān)聽器來接收這個事件:

通用監(jiān)聽器 - 會監(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)聽器 - 只會監(jiān)聽指定類型的數(shù)據(jù)。例如,如果我們只對字符串數(shù)據(jù)感興趣,我們可以如此定義:

package com.example.demo.listener;

import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;

/**
 * 特定數(shù)據(jù)類型的監(jiān)聽器。這個監(jiān)聽器專門監(jiān)聽String類型的PayloadApplicationEvent
 */
public class CustomStringApplicationListener implements ApplicationListener<PayloadApplicationEvent<String>> {
    
    @Override
    public void onApplicationEvent(PayloadApplicationEvent<String> event) {
        System.out.println("收到了字符串數(shù)據(jù):" + event.getPayload());
    }
}
  • 測試示例

要看這兩種監(jiān)聽器如何工作,我們來寫一個測試。

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ā)送一個字符串
        ctx.publishEvent(2023);              // 發(fā)送一個整數(shù)
        ctx.publishEvent(new Date());        // 發(fā)送一個日期對象
    }
}

在這個測試中,我們發(fā)送了三種類型的數(shù)據(jù):一個字符串、一個整數(shù)和一個日期。

執(zhí)行結果如下:

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

從輸出可以看出:

第一種監(jiān)聽器(通用的)接收到了所有三個事件,因為它不關心數(shù)據(jù)的具體類型。
第二種監(jiān)聽器(字符串專用的)只接收到了字符串類型的事件。

3. 為什么選擇自定義事件?

??雖然PayloadApplicationEvent提供了簡化事件監(jiān)聽的能力,但其可能不足以滿足特定的業(yè)務需求,尤其是當需要更多上下文和數(shù)據(jù)時。下面是一個使用自定義事件ArticlePublishedEvent的例子。

全部代碼如下:

自定義事件: ArticlePublishedEvent

這個事件代表了“新文章發(fā)布”,附帶有文章的標題、作者和發(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

這個監(jiān)聽器專門響應ArticlePublishedEvent,執(zhí)行特定的業(yè)務邏輯,例如通知訂閱者、更新搜索索引等。

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());
    }
}

在接收到新文章發(fā)布的事件后,監(jiān)聽器ArticlePublishedListener需要執(zhí)行以下業(yè)務邏輯:

  • 通知所有訂閱新文章通知的用戶。

  • 將新文章的標題、作者和發(fā)布日期添加到搜索引擎的索引中,以便用戶可以搜索到這篇新文章。

  • 更新統(tǒng)計信息,例如總文章數(shù)、最近發(fā)布的文章等。

??這樣,每次新文章發(fā)布的事件被觸發(fā)時,訂閱者都會被通知,搜索引擎的索引將會得到更新,同時相關的統(tǒng)計數(shù)據(jù)也會得到更新。

主程序

模擬文章發(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"));
    }
}

運行結果如下:

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

??我們可以看到ArticlePublishedEventPayloadApplicationEvent具有更多的業(yè)務含義和上下文。這樣的設計使我們能夠更具體地響應和處理特定的業(yè)務事件。

??實際上,在企業(yè)級應用中,文章發(fā)布可能會觸發(fā)多種不同的后續(xù)動作,使用Spring的事件監(jiān)聽器模式可以帶來如下優(yōu)勢:

  • 解耦:事件發(fā)布者(即新文章發(fā)布功能)不必關心具體的后續(xù)處理步驟。它只需發(fā)布事件,然后其他感興趣的監(jiān)聽器會相應地做出響應。這種設計有助于各個功能之間的解耦。

  • 可擴展性:如果未來需要為新文章發(fā)布添加更多的后續(xù)處理,只需添加更多的監(jiān)聽器即可,無需修改原有的業(yè)務邏輯。

  • 維護性:由于功能之間的解耦,每個功能模塊都可以獨立維護,這有助于提高代碼的可維護性。

??Spring為開發(fā)者提供了強大的事件監(jiān)聽機制,無論是使用自定義事件還是利用PayloadApplicationEvent進行快速開發(fā),都使我們能夠構建一個高度解耦、可擴展且易于維護的系統(tǒng)。

4. 事件廣播原理

4.1 Spring 5.x的事件模型概述

  1. 核心概念
  • ApplicationEvent:這是所有Spring事件的超類。用戶可以通過繼承此類來創(chuàng)建自定義事件。

  • ApplicationListener:這是所有事件監(jiān)聽器的接口。它定義了一個onApplicationEvent方法,用于處理特定類型的事件。

  • ApplicationEventPublisher:這是一個接口,定義了發(fā)布事件的方法。ApplicationContext繼承了這個接口,因此任何Spring bean都可以發(fā)布事件。

  • ApplicationEventMulticaster:這個組件負責將事件廣播到所有匹配的監(jiān)聽器。

  1. 事件發(fā)布

??用戶可以通過ApplicationEventPublisher接口或ApplicationContext來發(fā)布事件。通常情況下,當我們在Spring bean中需要發(fā)布事件時,可以讓這個bean實現(xiàn)ApplicationEventPublisherAware接口,這樣Spring容器會注入一個事件發(fā)布器。

  1. 異步事件

??從Spring 4.2開始,我們可以輕松地使事件監(jiān)聽器異步化。在Spring 5中,這一功能仍然得到支持。只需要在監(jiān)聽器方法上添加@Async注解并確保啟用了異步支持。這使得事件處理可以在單獨的線程中執(zhí)行,不阻塞發(fā)布者。

  1. 泛型事件

??Spring 4.2引入了對泛型事件的支持,這在Spring 5中得到了維護。這意味著監(jiān)聽器現(xiàn)在可以根據(jù)事件的泛型類型進行過濾。例如,一個ApplicationListener<ApplicationEvent<String>>將只接收到攜帶String負載的事件。

  1. 事件的排序

??監(jiān)聽器可以實現(xiàn)Ordered接口或使用@Order注解來指定事件的執(zhí)行順序。

  1. 新的事件類型

??Spring 5引入了新的事件類型,如ServletRequestHandledEvent,為web請求處理提供更多的鉤子。而像ContextRefreshedEvent這樣的事件,雖然不是Spring 5新引入的,但它為特定的生命周期回調提供了鉤子。

  1. Reactive事件模型

??與Spring 5引入的WebFlux一起,還引入了對反應式編程模型的事件監(jiān)聽和發(fā)布的支持。

總結
??在Spring 5.x中,事件模型得到了進一步的增強和優(yōu)化,增加了對異步、泛型和反應式編程的支持,提供了更強大、靈活和高效的機制來處理應用程序事件。對于開發(fā)者來說,這為在解耦的同時實現(xiàn)復雜的業(yè)務邏輯提供了便利。

4.2 發(fā)布事件publishEvent源碼分析

上圖,這里是Spring 5.3.7的源碼,下面講單獨抽出來分析

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

public void publishEvent(ApplicationEvent event) {
    this.publishEvent(event, (ResolvableType)null);
}

分析:
??該方法接受一個ApplicationEvent對象并調用其重載版本publishEvent(Object event, @Nullable ResolvableType eventType),為其傳遞null作為事件類型。這是為了簡化用戶使用,用戶可以直接傳遞一個ApplicationEvent對象而無需考慮其具體的類型。

public void publishEvent(Object event) {
    this.publishEvent(event, (ResolvableType)null);
}

分析:
??與上一個方法類似,但它接受任何Object作為事件,并將其與nulleventType一起傳遞給核心方法。這增加了靈活性,用戶可以發(fā)送任何對象作為事件,而不僅僅是ApplicationEvent對象。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    // 檢查事件對象是否為空,確保發(fā)布的事件是有意義的
    Assert.notNull(event, "Event must not be null");

    // 判斷傳入的事件是否已經(jīng)是ApplicationEvent類型,如果是,則無需再進行包裝
    Object applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent)event;
    } else {
        // 如果傳入的事件不是ApplicationEvent類型,則將其包裝為PayloadApplicationEvent
        applicationEvent = new PayloadApplicationEvent(this, event);
        // 如果未指定事件類型,那么從包裝后的事件中獲取其真實類型
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
        }
    }

    // 判斷當前是否存在earlyApplicationEvents列表
    if (this.earlyApplicationEvents != null) {
        // 如果存在,說明ApplicationContext還未完全初始化,將事件添加到此列表中,稍后再進行處理
        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類型,則帶上事件類型進行發(fā)布
            ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
        } else {
            // 否則,只傳遞事件對象進行發(fā)布
            this.parent.publishEvent(event);
        }
    }
}

這個方法究竟做了什么?

  1. 事件非空檢查:為了確保事件對象不為空,進行了初步的斷言檢查。這是一個常見的做法,以防止無效的事件被廣播。

  2. 事件類型檢查與封裝:Spring允許使用任意類型的對象作為事件。如果傳入的不是ApplicationEvent的實例,它會使用PayloadApplicationEvent來進行封裝。這種設計提供了更大的靈活性。

  3. 早期事件的處理:在Spring的生命周期中,ApplicationContext可能還沒有完全初始化,這時會有一些早期的事件。如果earlyApplicationEvents不為空,這些事件會被添加到此列表中,稍后再廣播。

  4. 事件廣播:如果ApplicationContext已初始化,事件會被廣播給所有的監(jiān)聽器。這是通過ApplicationEventMulticaster完成的,它是Spring中負責事件廣播的核心組件。

  5. 處理父ApplicationContext:在有些應用中,可以存在父子ApplicationContext。當子容器廣播一個事件時,也可以考慮在父容器中廣播這個事件。這是為了確保在整個上下文層次結構中的所有感興趣的監(jiān)聽器都能收到事件。

通過這種方式,Spring的事件發(fā)布機制確保了事件在不同的上下文和生命周期階段都能被正確處理和廣播。

上面說到事件廣播是ApplicationEventMulticaster完成的,這個是什么?下面來看看

4.3 Spring事件廣播:從ApplicationEventMulticaster開始

??當我們在Spring中討論事件,我們實際上是在討論兩件事:事件(即發(fā)生的事情)和監(jiān)聽器(即對這些事件感興趣并作出反應的實體)。

??ApplicationEventMulticaster的主要職責是管理事件監(jiān)聽器并廣播事件給這些監(jiān)聽器。我們主要關注SimpleApplicationEventMulticaster,因為這是默認的實現(xiàn),但請注意,Spring允許替換為自定義的ApplicationEventMulticaster。

以下是SimpleApplicationEventMulticaster中的相關代碼與分析,有興趣的小伙伴可以自行查看:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    // 可選的任務執(zhí)行器,用于異步調用事件監(jiān)聽器。
    @Nullable
    private Executor taskExecutor;

    // 可選的錯誤處理器,用于處理在廣播事件過程中出現(xiàn)的錯誤。
    @Nullable
    private ErrorHandler errorHandler;

    // 用于記錄日志的logger,它是延遲初始化的。
    @Nullable
    private volatile Log lazyLogger;

    // 默認構造函數(shù)。
    public SimpleApplicationEventMulticaster() {
    }

    // 帶有BeanFactory參數(shù)的構造函數(shù),通常用于更復雜的應用上下文配置中。
    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        this.setBeanFactory(beanFactory);
    }

    // 設置任務執(zhí)行器??梢允侨魏蜫ava Executor,比如Spring的SimpleAsyncTaskExecutor或Java的FixedThreadPool。
    public void setTaskExecutor(@Nullable Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    // 獲取當前設置的任務執(zhí)行器。
    @Nullable
    protected Executor getTaskExecutor() {
        return this.taskExecutor;
    }

    // 設置錯誤處理器。
    public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    // 獲取當前設置的錯誤處理器。
    @Nullable
    protected ErrorHandler getErrorHandler() {
        return this.errorHandler;
    }

    // 這是廣播事件的主要方法。它首先解析事件的類型,然后調用具有額外參數(shù)的重載方法。
    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }

    // 這個方法是真正執(zhí)行廣播操作的方法。
    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        // 確定事件類型。
        ResolvableType type = (eventType != null) ? eventType : this.resolveDefaultEventType(event);
        // 獲取任務執(zhí)行器。
        Executor executor = this.getTaskExecutor();
        // 獲取匹配此事件類型的所有監(jiān)聽器。
        Iterator<ApplicationListener<?>> listeners = this.getApplicationListeners(event, type).iterator();

        // 遍歷每個監(jiān)聽器并調用它。
        while(listeners.hasNext()) {
            ApplicationListener<?> listener = listeners.next();
            // 如果有設置任務執(zhí)行器,則異步調用監(jiān)聽器。
            if (executor != null) {
                executor.execute(() -> this.invokeListener(listener, event));
            } else {
                // 如果沒有設置任務執(zhí)行器,則同步調用監(jiān)聽器。
                this.invokeListener(listener, event);
            }
        }
    }

    // 為給定的事件解析默認的事件類型。
    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
        return ResolvableType.forInstance(event);
    }

    // 調用指定的監(jiān)聽器來處理給定的事件,并根據(jù)需要處理錯誤。
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        // 獲取當前的錯誤處理器。
        ErrorHandler errorHandler = this.getErrorHandler();
        if (errorHandler != null) {
            try {
                // 嘗試調用監(jiān)聽器。
                this.doInvokeListener(listener, event);
            } catch (Throwable ex) {
                // 如果出現(xiàn)錯誤,使用錯誤處理器處理。
                errorHandler.handleError(ex);
            }
        } else {
            // 如果沒有設置錯誤處理器,則直接調用監(jiān)聽器。
            this.doInvokeListener(listener, event);
        }
    }

    // 直接調用監(jiān)聽器,捕獲任何類型不匹配的異常。
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException ex) {
            // 捕獲類型轉換異常,并根據(jù)需要進行處理。
            // 這可以確保如果監(jiān)聽器不能處理特定類型的事件,不會導致整個廣播操作失敗。
            String msg = ex.getMessage();
            if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
                throw ex;
            }
            
            // 在預期情況下捕獲并記錄異常,而不是拋出它。
            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ù)給定的類型錯誤消息和事件類來檢查ClassCastException是否是預期的。
    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);
        }
    }
}

關于核心方法multicastEvent需要作出特別說明:

    // 這個方法是真正執(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();
        ......
    }

??方法第一行得到一個ResolvableType類型的對象,為什么 Spring 選擇使用 ResolvableType 而不是直接使用 Java 類型?最主要的原因是 Java 的泛型擦除。Java 中,泛型只存在于編譯時,一旦代碼被編譯,泛型信息就會被擦除,運行時就不能直接獲取到泛型的實際類型。

??為了解決這個問題,Spring 引入了 ResolvableType,一個能夠解析泛型類型信息的工具類。

舉個例子:

假設有如下的類定義:

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 事件中的使用

??ResolvableTypeSpring 事件中的應用主要是確定事件的類型和監(jiān)聽器監(jiān)聽的事件類型。當我們發(fā)布一個事件:

ApplicationEvent event = new MyCustomEvent(this, "data");
applicationContext.publishEvent(event);

??Spring 內部會使用 ResolvableType.forInstance(event) 來獲取這個事件的類型。然后,它會找到所有注冊的監(jiān)聽器,查看它們監(jiān)聽的事件類型是否與此事件匹配(通過比較 ResolvableType)。匹配的監(jiān)聽器會被調用。

對于一個監(jiān)聽器:

public class MyListener implements ApplicationListener<MyCustomEvent> {
    @Override
    public void onApplicationEvent(MyCustomEvent event) {
        // handle the event
    }
}

Spring 內部會使用 ResolvableType 來解析這個監(jiān)聽器監(jiān)聽的事件類型(在這個例子中是 MyCustomEvent)。

總之,ResolvableTypeSpring 中的主要用途是提供了一種方式來解析和操作運行時的泛型類型信息,特別是在事件發(fā)布和監(jiān)聽中。

4.4 Spring事件發(fā)布與處理流程圖

如果看不清,建議在新標簽頁中打開圖片后放大看
spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

4.5 監(jiān)聽器內部邏輯

再來看看監(jiān)聽器內部邏輯,我們來分析在multicastEvent方法中調用的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);
    // 初始化一個新的監(jiān)聽器檢索器為null
    AbstractApplicationEventMulticaster.CachedListenerRetriever newRetriever = null;
    // 嘗試從緩存中使用鍵取得一個已存在的檢索器
    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)建一個新的檢索器
        newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();
        // 嘗試將新檢索器添加到緩存中
        existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
        // 如果緩存中已經(jīng)有了一個值(由于并發(fā)的原因),則將新檢索器設回null
        if (existingRetriever != null) {
            newRetriever = null;
        }
    }

    // 如果有現(xiàn)有的檢索器
    if (existingRetriever != null) {
        // 嘗試從檢索器中獲取監(jiān)聽器集合
        Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
        // 如果結果不為null,則直接返回
        if (result != null) {
            return result;
        }
    }

    // 如果上述步驟都沒有返回,調用retrieveApplicationListeners進行實際的監(jiān)聽器檢索
    return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.CachedListenerRetriever retriever) {
    // 初始化一個空的監(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實例支持事件,并且它還沒有被加入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ù)下一個
            }
        }
    }

    // 對allListeners列表進行排序,確保監(jiān)聽器的執(zhí)行順序
    AnnotationAwareOrderComparator.sort(allListeners);
    // 如果retriever非null,更新其內部集合以后續(xù)使用
    if (retriever != null) {
        if (filteredListenerBeans.isEmpty()) {
            retriever.applicationListeners = new LinkedHashSet(allListeners);
            retriever.applicationListenerBeans = filteredListenerBeans;
        } else {
            retriever.applicationListeners = filteredListeners;
            retriever.applicationListenerBeans = filteredListenerBeans;
        }
    }

    // 返回allListeners作為結果
    return allListeners;
}

監(jiān)聽器內部做了什么?

getApplicationListeners方法中,采用了一種優(yōu)化檢索的緩存機制來提高性能并確保線程安全性。

具體分析如下:

  1. 首次檢查:
AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever = 
    (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);

這里,我們首先從retrieverCache中檢索existingRetriever。

  1. 判斷是否需要進入同步代碼塊:
if (existingRetriever == null && (this.beanClassLoader == null || ...)) {
    ...
}

如果existingRetriever為空,那么我們可能需要創(chuàng)建一個新的CachedListenerRetriever并放入緩存中。但是,為了確保線程安全性,我們必須在這之前進行進一步的檢查。

  1. 雙重檢查: 在創(chuàng)建新的CachedListenerRetriever之前,我們使用了putIfAbsent方法。這個方法會嘗試添加一個新值,但如果該值已存在,它只會返回現(xiàn)有的值。該機制采用了一種緩存優(yōu)化策略:通過ConcurrentMapputIfAbsent方法,即使多個線程同時到達這個代碼段,也確保只有一個線程能夠成功地放入新的值,從而保證線程安全性。
newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();
existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);

這里的邏輯使用了ConcurrentMapputIfAbsent方法來確保線程安全性,而沒有使用傳統(tǒng)的synchronized塊。

所以,我們可以說getApplicationListeners中的這部分邏輯采用了一種優(yōu)化檢索的緩存機制。它利用了并發(fā)容器的原子性操作putIfAbsent來保證線程安全,而不是依賴于傳統(tǒng)的雙重檢查鎖定模式。

總體概括一下,對于getApplicationListeners和retrieveApplicationListeners兩個方法的功能可以總結為以下三個步驟

  1. 從默認檢索器篩選監(jiān)聽器
    這部分代碼直接從defaultRetriever中獲取監(jiān)聽器,并檢查它們是否支持當前事件。在retrieveApplicationListeners方法中,代碼首先從defaultRetriever中獲取已經(jīng)編程式注入的監(jiān)聽器,并檢查每個監(jiān)聽器是否支持當前的事件類型。
listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
for (ApplicationListener<?> listener : listeners) {
    if (this.supportsEvent(listener, eventType, sourceType)) {
        ... // 添加到篩選出來的監(jiān)聽器列表
    }
}
  1. 從IOC容器中篩選監(jiān)聽器

retrieveApplicationListeners方法中,除了從defaultRetriever中獲取已經(jīng)編程式注入的監(jiān)聽器,代碼還會嘗試從IOC容器(通過bean名稱)獲取監(jiān)聽器,并檢查它們是否支持當前的事件。

if (!listenerBeans.isEmpty()) {
    ConfigurableBeanFactory beanFactory = this.getBeanFactory();
    for (String listenerBeanName : listenerBeans) {
        ... // 檢查并添加到篩選出來的監(jiān)聽器列表
    }
}
  1. 監(jiān)聽器排序

最后,為確保監(jiān)聽器按照預定的順序響應事件,篩選出的所有監(jiān)聽器會經(jīng)過排序。排序基于Spring@Order注解或Ordered接口,如AnnotationAwareOrderComparator.sort(allListeners)所示

AnnotationAwareOrderComparator.sort(allListeners);

4.6 Spring事件監(jiān)聽器檢索流程圖

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

5. Spring事件傳播、異步處理等機制的詳細圖示

spring事件源碼,Spring高手之路,1024程序員節(jié),payloadevent,Spring事件,Spring監(jiān)聽器,事件源碼,Spring事件廣播,原力計劃

說明:

  1. 容器和事件廣播:
  • ApplicationContextSpring的應用上下文容器。在圖中,我們有一個主容器和一個子容器。
    當我們想發(fā)布一個事件時,我們調用 publishEvent 方法。

  • ApplicationEventMulticaster 負責實際地將事件廣播到各個監(jiān)聽器。

  1. 主容器和子容器關系:
  • Spring中,可以有多個容器,其中一個是主容器,其他的則是子容器。

  • 通常,子容器可以訪問主容器中的bean,但反之則不行。但在事件傳播的上下文中,子容器發(fā)布的事件默認不會在主容器中傳播。這一點由 Note1 注釋標明。

  1. 異步處理:
  • 當事件被發(fā)布時,它可以被異步地傳播到監(jiān)聽器,這取決于是否配置了異步執(zhí)行器。

  • 是否使用異步執(zhí)行器? 這個決策點說明了基于配置,事件可以同步或異步地傳播到監(jiān)聽器。

  1. 事件生命周期:
  • Spring容器的生命周期中,有些事件在容器初始化前觸發(fā),這些被稱為 early events。這些事件會被緩存起來,直到容器初始化完成。

  • 一旦容器初始化完成,這些早期的事件會被處理,并開始處理常規(guī)事件。

  • 在容器銷毀時,也可能觸發(fā)事件。文章來源地址http://www.zghlxwxcb.cn/news/detail-734656.html

  1. 注意事項 (Note1):
  • 這個部分強調了一個特定的行為,即在某些配置下,子容器發(fā)布的事件可能也會在主容器中傳播,但這并不是默認行為。

歡迎一鍵三連~

有問題請留言,大家一起探討學習

----------------------Talk is cheap, show me the code-----------------------

到了這里,關于Spring高手之路15——掌握Spring事件監(jiān)聽器的內部邏輯與實現(xiàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Qt實現(xiàn)全局鼠標事件監(jiān)聽器-Windows

    Qt實現(xiàn)全局鼠標事件監(jiān)聽器-Windows

    更多精彩內容 ??個人內容分類匯總 ?? ??Qt自定義模塊、工具?? Qt版本:V5.12.5 兼容系統(tǒng): Windows:這里測試了Windows10,其它的版本沒有測試; Linux:這里測試了ubuntu18.04、20.04,其它的沒有測試; Mac:等啥時候我有了Mac電腦再說。 有時候我們想獲取到【系統(tǒng)全局鼠標事件】

    2024年02月16日
    瀏覽(15)
  • Qt實現(xiàn)全局鍵盤事件監(jiān)聽器-Windows

    Qt實現(xiàn)全局鍵盤事件監(jiān)聽器-Windows

    更多精彩內容 ??個人內容分類匯總 ?? ??Qt自定義模塊、工具?? Qt版本:V5.12.5 兼容系統(tǒng): Windows:這里測試了Windows10,其它的版本沒有測試; Linux:這里測試了ubuntu18.04、20.04,其它的沒有測試; Mac:等啥時候我有了Mac電腦再說。 有時候我們想獲取到全局鍵盤事件,使用

    2024年02月09日
    瀏覽(30)
  • Qt實現(xiàn)全局鼠標事件監(jiān)聽器-Linux

    Qt實現(xiàn)全局鼠標事件監(jiān)聽器-Linux

    更多精彩內容 ??個人內容分類匯總 ?? ??Qt自定義模塊、工具?? Qt版本:V5.12.5 兼容系統(tǒng): Windows:這里測試了Windows10,其它的版本沒有測試; Linux:這里測試了ubuntu18.04、20.04,其它的沒有測試; Mac:等啥時候我有了Mac電腦再說。 有時候我們想獲取到【系統(tǒng)全局鼠標事件】

    2024年02月11日
    瀏覽(17)
  • Unity UGUI的EventTrigger (事件監(jiān)聽器)組件的介紹及使用

    EventTrigger是Unity UGUI中的一個組件,用于監(jiān)聽和響應UI元素的各種事件,例如點擊、拖拽、進入、離開等。通過EventTrigger組件,我們可以方便地為UI元素添加各種交互行為。 EventTrigger組件通過監(jiān)聽UI元素上的事件,并在事件觸發(fā)時執(zhí)行相應的回調函數(shù)。每個UI元素可以添加多個

    2024年02月15日
    瀏覽(20)
  • 【Spring Boot】Spring—加載監(jiān)聽器

    【Spring Boot】Spring—加載監(jiān)聽器

    前幾天的時候,項目里有一個需求,需要一個開關控制代碼中是否執(zhí)行一段邏輯,于是理所當然的在yml文件中配置了一個屬性作為開關,再配合nacos就可以隨時改變這個值達到我們的目的,yml文件中是這樣寫的: 程序中的代碼也很簡單,大致的邏輯就是下面這樣,如果取到的

    2024年02月08日
    瀏覽(23)
  • Spring Boot 監(jiān)聽器詳解

    Spring Boot 監(jiān)聽器詳解

    Spring Boot 3.x系列文章 Spring Boot 2.7.8 中文參考指南(一) Spring Boot 2.7.8 中文參考指南(二)-Web Spring Boot 源碼閱讀初始化環(huán)境搭建 Spring Boot 框架整體啟動流程詳解 Spring Boot 系統(tǒng)初始化器詳解 Spring Boot 監(jiān)聽器詳解 通過前面的幾篇文章,我們都能看到 SpringApplicationRunListener ,SpringApp

    2024年02月08日
    瀏覽(27)
  • Spring中最簡單的過濾器和監(jiān)聽器

    Spring中最簡單的過濾器和監(jiān)聽器

    ? ??????Filter也稱之為過濾器,它是Servlet技術中最實用的技術,Web開發(fā)人員通過Filter技術,對web服務器管理的所有web資源:例如Jsp, Servlet, 靜態(tài)圖片文件或靜態(tài) html 文件等進行攔截,從而實現(xiàn)一些特殊的功能。例如實現(xiàn)URL級別的權限訪問控制、過濾敏感詞匯、壓縮響應信息

    2024年02月14日
    瀏覽(20)
  • Spring監(jiān)聽器用法與原理詳解(帶ApplicationListener模型圖)

    Spring監(jiān)聽器用法與原理詳解(帶ApplicationListener模型圖)

    相信大家都或多或少知道Spring中的監(jiān)聽器,有些人還能說出它采用了 觀察者模式 ,但其實它還用到了 適配器模式 , 工廠模式 等。當然,仍有不少人是完全不了解Spring的監(jiān)聽及其機制的,本次我們就來深入學習一下 Spring監(jiān)聽器 Spring監(jiān)聽器是一種 特殊的類,它們能幫助開發(fā)

    2024年02月06日
    瀏覽(31)
  • Spring項目配置文件中RabbitMQ監(jiān)聽器各個參數(shù)的作用

    spring.rabbitmq.listener.simple.concurrency :設置監(jiān)聽器容器的并發(fā)消費者數(shù)量,默認為1,即單線程消費。 spring.rabbitmq.listener.simple.max-concurrency :設置監(jiān)聽器容器的最大并發(fā)消費者數(shù)量。 spring.rabbitmq.listener.simple.prefetch :設置每個消費者從RabbitMQ服務器獲取的消息數(shù)量,即每次從隊列

    2024年02月16日
    瀏覽(36)
  • Spring Boot實戰(zhàn):攔截器和監(jiān)聽器的應用指南

    Spring Boot實戰(zhàn):攔截器和監(jiān)聽器的應用指南

    當使用Spring Boot時,我們可以通過攔截器(Interceptor)和監(jiān)聽器(Listener)來實現(xiàn)對請求和響應的處理。攔截器和監(jiān)聽器提供了一種可插拔的機制,用于在請求處理過程中進行自定義操作,例如記錄日志、身份驗證、權限檢查等。下面通過提供一個示例,展示如何使用攔截器和

    2024年02月09日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包