0


Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现

文章目录

在阅读本文之前需要你已经对事件监听器有了简单的了解,或去阅读前面的文章《Spring高手之路7——事件机制与监听器的全面探索

1. 事件的层次传播

  在

Spring

中,

ApplicationContext

可以形成一个层次结构,通常由主容器和多个子容器组成。一个常见的疑问是:当一个事件在其中一个容器中发布时,这个事件会如何在这个层次结构中传播?

  为了探讨这个问题,我们创建了一个名为

HierarchicalEventPropagationEvent

的事件类和一个对应的监听器

HierarchicalEventPropagationListener

全部代码如下:

packagecom.example.demo.event;importorg.springframework.context.ApplicationEvent;// 事件类publicclassHierarchicalEventPropagationEventextendsApplicationEvent{privateString message;publicHierarchicalEventPropagationEvent(Object source,String message){super(source);this.message = message;}publicStringgetMessage(){return message;}}

相应地,为

HierarchicalEventPropagationEvent

定义一个监听器

HierarchicalEventPropagationListener

packagecom.example.demo.listener;importcom.example.demo.event.HierarchicalEventPropagationEvent;importorg.springframework.context.ApplicationListener;// 监听器类publicclassHierarchicalEventPropagationListenerimplementsApplicationListener<HierarchicalEventPropagationEvent>{privateString listenerId;publicHierarchicalEventPropagationListener(String listenerId){this.listenerId = listenerId;}@OverridepublicvoidonApplicationEvent(HierarchicalEventPropagationEvent event){System.out.println(listenerId +" received event - "+ event.getMessage());}}

  为了测试继承机制,我们需要构建主容器和子容器,并为每个容器注册了一个监听器。初始化容器后,我们在两个容器中分别发布事件。

  请注意,首先需要刷新主容器,然后刷新子容器。否则会出现异常:

Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@somehashcode

主程序如下:

packagecom.example.demo;importcom.example.demo.event.HierarchicalEventPropagationEvent;importcom.example.demo.listener.HierarchicalEventPropagationListener;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){// 创建父容器,注册监听器AnnotationConfigApplicationContext parentCtx =newAnnotationConfigApplicationContext();
        parentCtx.addApplicationListener(newHierarchicalEventPropagationListener("Parent Listener"));
        parentCtx.refresh();// 创建子容器,注册监听器AnnotationConfigApplicationContext childCtx =newAnnotationConfigApplicationContext();
        childCtx.setParent(parentCtx);
        childCtx.addApplicationListener(newHierarchicalEventPropagationListener("Child Listener"));
        childCtx.refresh();// 发布事件HierarchicalEventPropagationEvent event1 =newHierarchicalEventPropagationEvent(parentCtx,"Event from parent");
        parentCtx.publishEvent(event1);HierarchicalEventPropagationEvent event2 =newHierarchicalEventPropagationEvent(childCtx,"Event from child");
        childCtx.publishEvent(event2);}}

运行结果

在这里插入图片描述

  主容器发布的事件只触发了一次监听,而子容器发布的事件触发了两次监听。父容器和子容器都监听到了来自子容器的事件,而只有父容器监听到了来自父容器的事件。

  所以得出结论:**在

Spring

的父子容器结构中,事件会从子容器向上传播至其父容器,但父容器中发布的事件不会向下传播至子容器。** 这种设计可以帮助开发者在父容器中集中处理所有的事件,而不必担心事件在多个子容器之间的传播。

2. PayloadApplicationEvent的使用

PayloadApplicationEvent

Spring

提供的一种特殊事件,用于传递数据(称为"

payload

")。所以不需要自定义事件,

PayloadApplicationEvent

可以直接传递任何类型的数据,只需要指定它的类型即可。

全部代码如下:

  • 定义监听器

首先,我们来看怎样定义一个监听器来接收这个事件:

通用监听器 - 会监听到所有种类的

PayloadApplicationEvent

packagecom.example.demo.listener;importorg.springframework.context.ApplicationListener;importorg.springframework.context.PayloadApplicationEvent;/**
 * 通用监听器,能监听到所有类型的PayloadApplicationEvent
 */publicclassCustomObjectApplicationListenerimplementsApplicationListener<PayloadApplicationEvent>{@OverridepublicvoidonApplicationEvent(PayloadApplicationEvent event){System.out.println("收到PayloadApplicationEvent,数据是:"+ event.getPayload());}}

特定数据类型的监听器 - 只会监听指定类型的数据。例如,如果我们只对字符串数据感兴趣,我们可以如此定义:

packagecom.example.demo.listener;importorg.springframework.context.ApplicationListener;importorg.springframework.context.PayloadApplicationEvent;/**
 * 特定数据类型的监听器。这个监听器专门监听String类型的PayloadApplicationEvent
 */publicclassCustomStringApplicationListenerimplementsApplicationListener<PayloadApplicationEvent<String>>{@OverridepublicvoidonApplicationEvent(PayloadApplicationEvent<String> event){System.out.println("收到了字符串数据:"+ event.getPayload());}}
  • 测试示例

要看这两种监听器如何工作,我们来写一个测试。

packagecom.example.demo;importcom.example.demo.listener.CustomObjectApplicationListener;importcom.example.demo.listener.CustomStringApplicationListener;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importjava.util.Date;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext ctx =newAnnotationConfigApplicationContext();// 注册监听器
        ctx.addApplicationListener(newCustomObjectApplicationListener());
        ctx.addApplicationListener(newCustomStringApplicationListener());
        ctx.refresh();// 发送事件
        ctx.publishEvent("Hello, World!");// 发送一个字符串
        ctx.publishEvent(2023);// 发送一个整数
        ctx.publishEvent(newDate());// 发送一个日期对象}}

在这个测试中,我们发送了三种类型的数据:一个字符串、一个整数和一个日期。

执行结果如下:

在这里插入图片描述

从输出可以看出:

第一种监听器(通用的)接收到了所有三个事件,因为它不关心数据的具体类型。
第二种监听器(字符串专用的)只接收到了字符串类型的事件。

3. 为什么选择自定义事件?

  虽然

PayloadApplicationEvent

提供了简化事件监听的能力,但其可能不足以满足特定的业务需求,尤其是当需要更多上下文和数据时。下面是一个使用自定义事件

ArticlePublishedEvent

的例子。

全部代码如下:

自定义事件: ArticlePublishedEvent

这个事件代表了“新文章发布”,附带有文章的标题、作者和发布日期等信息。

packagecom.example.demo.event;importorg.springframework.context.ApplicationEvent;publicclassArticlePublishedEventextendsApplicationEvent{privateString title;privateString author;privateString publishedDate;publicArticlePublishedEvent(Object source,String title,String author,String publishedDate){super(source);this.title = title;this.author = author;this.publishedDate = publishedDate;}publicStringgetTitle(){return title;}publicStringgetAuthor(){return author;}publicStringgetPublishedDate(){return publishedDate;}}

自定义监听器: ArticlePublishedListener

这个监听器专门响应

ArticlePublishedEvent

,执行特定的业务逻辑,例如通知订阅者、更新搜索索引等。

importorg.springframework.context.ApplicationListener;publicclassArticlePublishedListenerimplementsApplicationListener<ArticlePublishedEvent>{@OverridepublicvoidonApplicationEvent(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 articlenotifySubscribers(event);// Update search engine index with new article detailsupdateSearchIndex(event);// Update statistical data about articlesupdateStatistics(event);}privatevoidnotifySubscribers(ArticlePublishedEvent event){// Logic to notify subscribers (dummy logic for demonstration)System.out.println("Notifying subscribers about the new article titled: "+ event.getTitle());}privatevoidupdateSearchIndex(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());}privatevoidupdateStatistics(ArticlePublishedEvent event){// Logic to update statistical data (dummy logic for demonstration)System.out.println("Updating statistics with the new article titled: "+ event.getTitle());}}

在接收到新文章发布的事件后,监听器

ArticlePublishedListener

需要执行以下业务逻辑:

  • 通知所有订阅新文章通知的用户。
  • 将新文章的标题、作者和发布日期添加到搜索引擎的索引中,以便用户可以搜索到这篇新文章。
  • 更新统计信息,例如总文章数、最近发布的文章等。

  这样,每次新文章发布的事件被触发时,订阅者都会被通知,搜索引擎的索引将会得到更新,同时相关的统计数据也会得到更新。

主程序

模拟文章发布的场景

packagecom.example.demo;importcom.example.demo.event.ArticlePublishedEvent;importcom.example.demo.listener.ArticlePublishedListener;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){// Initialize Spring ApplicationContextAnnotationConfigApplicationContext ctx =newAnnotationConfigApplicationContext();// Register listener
        ctx.addApplicationListener(newArticlePublishedListener());
        ctx.refresh();// Simulate publishing an article
        ctx.publishEvent(newArticlePublishedEvent(ctx,"Spring Events","John Doe","2023-09-15"));}}

运行结果如下:

在这里插入图片描述

  我们可以看到

ArticlePublishedEvent

PayloadApplicationEvent

具有更多的业务含义和上下文。这样的设计使我们能够更具体地响应和处理特定的业务事件。

  实际上,在企业级应用中,文章发布可能会触发多种不同的后续动作,使用

Spring

的事件监听器模式可以带来如下优势:

  • 解耦:事件发布者(即新文章发布功能)不必关心具体的后续处理步骤。它只需发布事件,然后其他感兴趣的监听器会相应地做出响应。这种设计有助于各个功能之间的解耦。
  • 可扩展性:如果未来需要为新文章发布添加更多的后续处理,只需添加更多的监听器即可,无需修改原有的业务逻辑。
  • 维护性:由于功能之间的解耦,每个功能模块都可以独立维护,这有助于提高代码的可维护性。
Spring

为开发者提供了强大的事件监听机制,无论是使用自定义事件还是利用

PayloadApplicationEvent

进行快速开发,都使我们能够构建一个高度解耦、可扩展且易于维护的系统。

4. 事件广播原理

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

  1. 核心概念
  • ApplicationEvent:这是所有Spring事件的超类。用户可以通过继承此类来创建自定义事件。
  • ApplicationListener:这是所有事件监听器的接口。它定义了一个onApplicationEvent方法,用于处理特定类型的事件。
  • ApplicationEventPublisher:这是一个接口,定义了发布事件的方法。ApplicationContext继承了这个接口,因此任何Spring bean都可以发布事件。
  • ApplicationEventMulticaster:这个组件负责将事件广播到所有匹配的监听器。
  1. 事件发布

  用户可以通过

ApplicationEventPublisher

接口或

ApplicationContext

来发布事件。通常情况下,当我们在

Spring bean

中需要发布事件时,可以让这个

bean

实现

ApplicationEventPublisherAware

接口,这样

Spring

容器会注入一个事件发布器。

  1. 异步事件

  从

Spring 4.2

开始,我们可以轻松地使事件监听器异步化。在

Spring 5

中,这一功能仍然得到支持。只需要在监听器方法上添加

@Async

注解并确保启用了异步支持。这使得事件处理可以在单独的线程中执行,不阻塞发布者。

  1. 泛型事件
Spring 4.2

引入了对泛型事件的支持,这在

Spring 5

中得到了维护。这意味着监听器现在可以根据事件的泛型类型进行过滤。例如,一个

ApplicationListener<ApplicationEvent<String>>

将只接收到携带

String

负载的事件。

  1. 事件的排序

  监听器可以实现

Ordered

接口或使用

@Order

注解来指定事件的执行顺序。

  1. 新的事件类型
Spring 5

引入了新的事件类型,如

ServletRequestHandledEvent

,为

web

请求处理提供更多的钩子。而像

ContextRefreshedEvent

这样的事件,虽然不是

Spring 5

新引入的,但它为特定的生命周期回调提供了钩子。

  1. Reactive事件模型

  与

Spring 5

引入的

WebFlux

一起,还引入了对反应式编程模型的事件监听和发布的支持。

总结
  在

Spring 5.x

中,事件模型得到了进一步的增强和优化,增加了对异步、泛型和反应式编程的支持,提供了更强大、灵活和高效的机制来处理应用程序事件。对于开发者来说,这为在解耦的同时实现复杂的业务逻辑提供了便利。

4.2 发布事件publishEvent源码分析

上图,这里是

Spring 5.3.7

的源码,下面讲单独抽出来分析

在这里插入图片描述

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

分析:
  该方法接受一个

ApplicationEvent

对象并调用其重载版本

publishEvent(Object event, @Nullable ResolvableType eventType)

,为其传递

null

作为事件类型。这是为了简化用户使用,用户可以直接传递一个

ApplicationEvent

对象而无需考虑其具体的类型。

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

分析:
  与上一个方法类似,但它接受任何

Object

作为事件,并将其与

null

eventType

一起传递给核心方法。这增加了灵活性,用户可以发送任何对象作为事件,而不仅仅是

ApplicationEvent

对象。

protectedvoidpublishEvent(Object event,@NullableResolvableType eventType){// 检查事件对象是否为空,确保发布的事件是有意义的Assert.notNull(event,"Event must not be null");// 判断传入的事件是否已经是ApplicationEvent类型,如果是,则无需再进行包装Object applicationEvent;if(event instanceofApplicationEvent){
        applicationEvent =(ApplicationEvent)event;}else{// 如果传入的事件不是ApplicationEvent类型,则将其包装为PayloadApplicationEvent
        applicationEvent =newPayloadApplicationEvent(this, event);// 如果未指定事件类型,那么从包装后的事件中获取其真实类型if(eventType ==null){
            eventType =((PayloadApplicationEvent)applicationEvent).getResolvableType();}}// 判断当前是否存在earlyApplicationEvents列表if(this.earlyApplicationEvents !=null){// 如果存在,说明ApplicationContext还未完全初始化,将事件添加到此列表中,稍后再进行处理this.earlyApplicationEvents.add(applicationEvent);}else{// 如果ApplicationContext已经初始化,那么直接通过事件多播器广播事件this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);}// 如果存在父ApplicationContext,则也将事件发布到父容器中if(this.parent !=null){if(this.parent instanceofAbstractApplicationContext){// 如果父容器是AbstractApplicationContext类型,则带上事件类型进行发布((AbstractApplicationContext)this.parent).publishEvent(event, eventType);}else{// 否则,只传递事件对象进行发布this.parent.publishEvent(event);}}}

这个方法究竟做了什么?

  1. 事件非空检查:为了确保事件对象不为空,进行了初步的断言检查。这是一个常见的做法,以防止无效的事件被广播。
  2. 事件类型检查与封装:Spring允许使用任意类型的对象作为事件。如果传入的不是ApplicationEvent的实例,它会使用PayloadApplicationEvent来进行封装。这种设计提供了更大的灵活性。
  3. 早期事件的处理:在Spring的生命周期中,ApplicationContext可能还没有完全初始化,这时会有一些早期的事件。如果earlyApplicationEvents不为空,这些事件会被添加到此列表中,稍后再广播。
  4. 事件广播:如果ApplicationContext已初始化,事件会被广播给所有的监听器。这是通过ApplicationEventMulticaster完成的,它是Spring中负责事件广播的核心组件。
  5. 处理父ApplicationContext:在有些应用中,可以存在父子ApplicationContext。当子容器广播一个事件时,也可以考虑在父容器中广播这个事件。这是为了确保在整个上下文层次结构中的所有感兴趣的监听器都能收到事件。

通过这种方式,

Spring

的事件发布机制确保了事件在不同的上下文和生命周期阶段都能被正确处理和广播。

上面说到事件广播是

ApplicationEventMulticaster

完成的,这个是什么?下面来看看

4.3 Spring事件广播:从ApplicationEventMulticaster开始

  当我们在

Spring

中讨论事件,我们实际上是在讨论两件事:事件(即发生的事情)和监听器(即对这些事件感兴趣并作出反应的实体)。

ApplicationEventMulticaster

的主要职责是管理事件监听器并广播事件给这些监听器。我们主要关注

SimpleApplicationEventMulticaster

,因为这是默认的实现,但请注意,

Spring

允许替换为自定义的

ApplicationEventMulticaster

以下是

SimpleApplicationEventMulticaster

中的相关代码与分析,有兴趣的小伙伴可以自行查看:

publicclassSimpleApplicationEventMulticasterextendsAbstractApplicationEventMulticaster{// 可选的任务执行器,用于异步调用事件监听器。@NullableprivateExecutor taskExecutor;// 可选的错误处理器,用于处理在广播事件过程中出现的错误。@NullableprivateErrorHandler errorHandler;// 用于记录日志的logger,它是延迟初始化的。@NullableprivatevolatileLog lazyLogger;// 默认构造函数。publicSimpleApplicationEventMulticaster(){}// 带有BeanFactory参数的构造函数,通常用于更复杂的应用上下文配置中。publicSimpleApplicationEventMulticaster(BeanFactory beanFactory){this.setBeanFactory(beanFactory);}// 设置任务执行器。可以是任何Java Executor,比如Spring的SimpleAsyncTaskExecutor或Java的FixedThreadPool。publicvoidsetTaskExecutor(@NullableExecutor taskExecutor){this.taskExecutor = taskExecutor;}// 获取当前设置的任务执行器。@NullableprotectedExecutorgetTaskExecutor(){returnthis.taskExecutor;}// 设置错误处理器。publicvoidsetErrorHandler(@NullableErrorHandler errorHandler){this.errorHandler = errorHandler;}// 获取当前设置的错误处理器。@NullableprotectedErrorHandlergetErrorHandler(){returnthis.errorHandler;}// 这是广播事件的主要方法。它首先解析事件的类型,然后调用具有额外参数的重载方法。publicvoidmulticastEvent(ApplicationEvent event){this.multicastEvent(event,this.resolveDefaultEventType(event));}// 这个方法是真正执行广播操作的方法。publicvoidmulticastEvent(ApplicationEvent event,@NullableResolvableType eventType){// 确定事件类型。ResolvableType type =(eventType !=null)? eventType :this.resolveDefaultEventType(event);// 获取任务执行器。Executor executor =this.getTaskExecutor();// 获取匹配此事件类型的所有监听器。Iterator<ApplicationListener<?>> listeners =this.getApplicationListeners(event, type).iterator();// 遍历每个监听器并调用它。while(listeners.hasNext()){ApplicationListener<?> listener = listeners.next();// 如果有设置任务执行器,则异步调用监听器。if(executor !=null){
                executor.execute(()->this.invokeListener(listener, event));}else{// 如果没有设置任务执行器,则同步调用监听器。this.invokeListener(listener, event);}}}// 为给定的事件解析默认的事件类型。privateResolvableTyperesolveDefaultEventType(ApplicationEvent event){returnResolvableType.forInstance(event);}// 调用指定的监听器来处理给定的事件,并根据需要处理错误。protectedvoidinvokeListener(ApplicationListener<?> listener,ApplicationEvent event){// 获取当前的错误处理器。ErrorHandler errorHandler =this.getErrorHandler();if(errorHandler !=null){try{// 尝试调用监听器。this.doInvokeListener(listener, event);}catch(Throwable ex){// 如果出现错误,使用错误处理器处理。
                errorHandler.handleError(ex);}}else{// 如果没有设置错误处理器,则直接调用监听器。this.doInvokeListener(listener, event);}}// 直接调用监听器,捕获任何类型不匹配的异常。privatevoiddoInvokeListener(ApplicationListener listener,ApplicationEvent event){try{
            listener.onApplicationEvent(event);}catch(ClassCastException ex){// 捕获类型转换异常,并根据需要进行处理。// 这可以确保如果监听器不能处理特定类型的事件,不会导致整个广播操作失败。String msg = ex.getMessage();if(msg !=null&&!this.matchesClassCastMessage(msg, event.getClass())&&(!(event instanceofPayloadApplicationEvent)||!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);}}}// 根据给定的类型错误消息和事件类来检查ClassCastException是否是预期的。privatebooleanmatchesClassCastMessage(String classCastMessage,Class<?> eventClass){if(classCastMessage.startsWith(eventClass.getName())){returntrue;}elseif(classCastMessage.startsWith(eventClass.toString())){returntrue;}else{int moduleSeparatorIndex = classCastMessage.indexOf(47);return moduleSeparatorIndex !=-1&& classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex +1);}}}

关于核心方法multicastEvent需要作出特别说明:

// 这个方法是真正执行广播操作的方法。publicvoidmulticastEvent(ApplicationEvent event,@NullableResolvableType eventType){// 确定事件类型。ResolvableType type =(eventType !=null)? eventType :this.resolveDefaultEventType(event);......// 获取匹配此事件类型的所有监听器。Iterator<ApplicationListener<?>> listeners =this.getApplicationListeners(event, type).iterator();......}

  方法第一行得到一个

ResolvableType

类型的对象,为什么 Spring 选择使用 ResolvableType 而不是直接使用 Java 类型?最主要的原因是 Java 的泛型擦除。

Java

中,泛型只存在于编译时,一旦代码被编译,泛型信息就会被擦除,运行时就不能直接获取到泛型的实际类型。

  为了解决这个问题,Spring 引入了 ResolvableType,一个能够解析泛型类型信息的工具类

举个例子:

假设有如下的类定义:

publicclassSample{privateList<String> names;}

我们可以这样获取

names

字段的泛型类型:

ResolvableType t =ResolvableType.forField(Sample.class.getDeclaredField("names"));Class<?> genericType = t.getGeneric(0).resolve();// 得到 String.class

在 Spring 事件中的使用

ResolvableType

Spring

事件中的应用主要是确定事件的类型和监听器监听的事件类型。当我们发布一个事件:

ApplicationEvent event =newMyCustomEvent(this,"data");
applicationContext.publishEvent(event);
Spring

内部会使用

ResolvableType.forInstance(event)

来获取这个事件的类型。然后,它会找到所有注册的监听器,查看它们监听的事件类型是否与此事件匹配(通过比较

ResolvableType

)。匹配的监听器会被调用。

对于一个监听器:

publicclassMyListenerimplementsApplicationListener<MyCustomEvent>{@OverridepublicvoidonApplicationEvent(MyCustomEvent event){// handle the event}}
Spring

内部会使用

ResolvableType

来解析这个监听器监听的事件类型(在这个例子中是

MyCustomEvent

)。

总之,

ResolvableType

Spring

中的主要用途是提供了一种方式来解析和操作运行时的泛型类型信息,特别是在事件发布和监听中。

4.4 Spring事件发布与处理流程图

如果看不清,建议在新标签页中打开图片后放大看
在这里插入图片描述

4.5 监听器内部逻辑

再来看看监听器内部逻辑,我们来分析在

multicastEvent

方法中调用的

getApplicationListeners(event, type)

来分析下

protectedCollection<ApplicationListener<?>>getApplicationListeners(ApplicationEvent event,ResolvableType eventType){// 获取事件来源对象Object source = event.getSource();// 判断事件来源对象是否为null,是则返回null,否则返回事件来源对象的类Class<?> sourceType = source !=null? source.getClass():null;// 使用事件类型和源类型作为缓存键AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey =newAbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);// 初始化一个新的监听器检索器为nullAbstractApplicationEventMulticaster.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)))){// 创建一个新的检索器
        newRetriever =newAbstractApplicationEventMulticaster.CachedListenerRetriever();// 尝试将新检索器添加到缓存中
        existingRetriever =(AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);// 如果缓存中已经有了一个值(由于并发的原因),则将新检索器设回nullif(existingRetriever !=null){
            newRetriever =null;}}// 如果有现有的检索器if(existingRetriever !=null){// 尝试从检索器中获取监听器集合Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();// 如果结果不为null,则直接返回if(result !=null){return result;}}// 如果上述步骤都没有返回,调用retrieveApplicationListeners进行实际的监听器检索returnthis.retrieveApplicationListeners(eventType, sourceType, newRetriever);}privateCollection<ApplicationListener<?>>retrieveApplicationListeners(ResolvableType eventType,@NullableClass<?> sourceType,@NullableAbstractApplicationEventMulticaster.CachedListenerRetriever retriever){// 初始化一个空的监听器列表List<ApplicationListener<?>> allListeners =newArrayList();// 若retriever非null,则初始化集合来保存过滤出来的监听器和Bean名Set<ApplicationListener<?>> filteredListeners = retriever !=null?newLinkedHashSet():null;Set<String> filteredListenerBeans = retriever !=null?newLinkedHashSet():null;LinkedHashSet listeners;LinkedHashSet listenerBeans;// 同步从defaultRetriever中获取已注册的监听器和其Bean名称synchronized(this.defaultRetriever){
        listeners =newLinkedHashSet(this.defaultRetriever.applicationListeners);
        listenerBeans =newLinkedHashSet(this.defaultRetriever.applicationListenerBeans);}// 遍历所有的监听器for(ApplicationListener<?> listener : listeners){// 检查该监听器是否支持此事件类型和源类型if(this.supportsEvent(listener, eventType, sourceType)){if(retriever !=null){// 如果支持并且retriever非null,添加到过滤监听器集合
                filteredListeners.add(listener);}// 将支持的监听器添加到allListeners列表
            allListeners.add(listener);}}// 如果存在监听器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,添加到过滤监听器集合if(beanFactory.isSingleton(listenerBeanName)){
                                filteredListeners.add(listener);}else{
                                filteredListenerBeans.add(listenerBeanName);}}// 添加到allListeners列表
                        allListeners.add(listener);}}else{// 若不支持该事件,从allListeners中移除该BeanObject listener = beanFactory.getSingleton(listenerBeanName);if(retriever !=null){
                        filteredListeners.remove(listener);}
                    allListeners.remove(listener);}}catch(NoSuchBeanDefinitionException e){// 若Bean不存在,直接继续下一个}}}// 对allListeners列表进行排序,确保监听器的执行顺序AnnotationAwareOrderComparator.sort(allListeners);// 如果retriever非null,更新其内部集合以后续使用if(retriever !=null){if(filteredListenerBeans.isEmpty()){
            retriever.applicationListeners =newLinkedHashSet(allListeners);
            retriever.applicationListenerBeans = filteredListenerBeans;}else{
            retriever.applicationListeners = filteredListeners;
            retriever.applicationListenerBeans = filteredListenerBeans;}}// 返回allListeners作为结果return allListeners;}

监听器内部做了什么?

getApplicationListeners

方法中,采用了一种优化检索的缓存机制来提高性能并确保线程安全性。

具体分析如下:

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

这里,我们首先从

retrieverCache

中检索

existingRetriever

  1. 判断是否需要进入同步代码块:
if(existingRetriever ==null&&(this.beanClassLoader ==null||...)){...}

如果

existingRetriever

为空,那么我们可能需要创建一个新的

CachedListenerRetriever

并放入缓存中。但是,为了确保线程安全性,我们必须在这之前进行进一步的检查。

  1. 双重检查: 在创建新的CachedListenerRetriever之前,我们使用了putIfAbsent方法。这个方法会尝试添加一个新值,但如果该值已存在,它只会返回现有的值。该机制采用了一种缓存优化策略:通过ConcurrentMapputIfAbsent方法,即使多个线程同时到达这个代码段,也确保只有一个线程能够成功地放入新的值,从而保证线程安全性。
newRetriever =newAbstractApplicationEventMulticaster.CachedListenerRetriever();
existingRetriever =(AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);

这里的逻辑使用了

ConcurrentMap

putIfAbsent

方法来确保线程安全性,而没有使用传统的

synchronized

块。

所以,我们可以说

getApplicationListeners

中的这部分逻辑采用了一种优化检索的缓存机制。它利用了并发容器的原子性操作

putIfAbsent

来保证线程安全,而不是依赖于传统的双重检查锁定模式。

总体概括一下,对于getApplicationListeners和retrieveApplicationListeners两个方法的功能可以总结为以下三个步骤

  1. 从默认检索器筛选监听器: 这部分代码直接从defaultRetriever中获取监听器,并检查它们是否支持当前事件。在retrieveApplicationListeners方法中,代码首先从defaultRetriever中获取已经编程式注入的监听器,并检查每个监听器是否支持当前的事件类型。
listeners =newLinkedHashSet(this.defaultRetriever.applicationListeners);for(ApplicationListener<?> listener : listeners){if(this.supportsEvent(listener, eventType, sourceType)){...// 添加到筛选出来的监听器列表}}
  1. 从IOC容器中筛选监听器

retrieveApplicationListeners

方法中,除了从

defaultRetriever

中获取已经编程式注入的监听器,代码还会尝试从

IOC

容器(通过

bean

名称)获取监听器,并检查它们是否支持当前的事件。

if(!listenerBeans.isEmpty()){ConfigurableBeanFactory beanFactory =this.getBeanFactory();for(String listenerBeanName : listenerBeans){...// 检查并添加到筛选出来的监听器列表}}
  1. 监听器排序

最后,为确保监听器按照预定的顺序响应事件,筛选出的所有监听器会经过排序。排序基于

Spring

@Order

注解或

Ordered

接口,如

AnnotationAwareOrderComparator.sort(allListeners)

所示

AnnotationAwareOrderComparator.sort(allListeners);

4.6 Spring事件监听器检索流程图

在这里插入图片描述

5. Spring事件传播、异步处理等机制的详细图示

在这里插入图片描述

说明:

  1. 容器和事件广播:
  • ApplicationContextSpring的应用上下文容器。在图中,我们有一个主容器和一个子容器。 当我们想发布一个事件时,我们调用 publishEvent 方法。
  • ApplicationEventMulticaster 负责实际地将事件广播到各个监听器。
  1. 主容器和子容器关系:
  • Spring中,可以有多个容器,其中一个是主容器,其他的则是子容器。
  • 通常,子容器可以访问主容器中的bean,但反之则不行。但在事件传播的上下文中,子容器发布的事件默认不会在主容器中传播。这一点由 Note1 注释标明。
  1. 异步处理:
  • 当事件被发布时,它可以被异步地传播到监听器,这取决于是否配置了异步执行器。
  • 是否使用异步执行器? 这个决策点说明了基于配置,事件可以同步或异步地传播到监听器。
  1. 事件生命周期:
  • Spring容器的生命周期中,有些事件在容器初始化前触发,这些被称为 early events。这些事件会被缓存起来,直到容器初始化完成。
  • 一旦容器初始化完成,这些早期的事件会被处理,并开始处理常规事件。
  • 在容器销毁时,也可能触发事件。
  1. 注意事项 (Note1):
  • 这个部分强调了一个特定的行为,即在某些配置下,子容器发布的事件可能也会在主容器中传播,但这并不是默认行为。

欢迎一键三连~

有问题请留言,大家一起探讨学习

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


本文转载自: https://blog.csdn.net/qq_34115899/article/details/134012166
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。

“Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现”的评论:

还没有评论