当前位置:首页 > 科技  > 软件

掌握Spring事件监听器的内部逻辑与实现

来源: 责编: 时间:2023-11-01 09:18:58 191观看
导读1. 事件的层次传播 在Spring中,ApplicationContext可以形成一个层次结构,通常由主容器和多个子容器组成。一个常见的疑问是:当一个事件在其中一个容器中发布时,这个事件会如何在这个层次结构中传播? 为了探讨这个问题,我

1. 事件的层次传播

 在Spring中,ApplicationContext可以形成一个层次结构,通常由主容器和多个子容器组成。一个常见的疑问是:当一个事件在其中一个容器中发布时,这个事件会如何在这个层次结构中传播?fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 为了探讨这个问题,我们创建了一个名为HierarchicalEventPropagationEvent的事件类和一个对应的监听器HierarchicalEventPropagationListener。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

全部代码如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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 定义一个监听器HierarchicalEventPropagationListener :fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

package com.example.demo.listener;import com.example.demo.event.HierarchicalEventPropagationEvent;import org.springframework.context.ApplicationListener;// 监听器类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());    }}

 为了测试继承机制,我们需要构建主容器和子容器,并为每个容器注册了一个监听器。初始化容器后,我们在两个容器中分别发布事件。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 请注意,首先需要刷新主容器,然后刷新子容器。否则会出现异常:Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@somehashcodefXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

主程序如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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) {        // 创建父容器,注册监听器        AnnotationConfigApplicationContext parentCtx = new AnnotationConfigApplicationContext();        parentCtx.addApplicationListener(new HierarchicalEventPropagationListener("Parent Listener"));        parentCtx.refresh();        // 创建子容器,注册监听器        AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();        childCtx.setParent(parentCtx);        childCtx.addApplicationListener(new HierarchicalEventPropagationListener("Child Listener"));        childCtx.refresh();        // 发布事件        HierarchicalEventPropagationEvent event1 = new HierarchicalEventPropagationEvent(parentCtx, "Event from parent");        parentCtx.publishEvent(event1);        HierarchicalEventPropagationEvent event2 = new HierarchicalEventPropagationEvent(childCtx, "Event from child");        childCtx.publishEvent(event2);    }}

运行结果

fXj28资讯网——每日最新资讯28at.com


fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 主容器发布的事件只触发了一次监听,而子容器发布的事件触发了两次监听。父容器和子容器都监听到了来自子容器的事件,而只有父容器监听到了来自父容器的事件。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 所以得出结论:在Spring的父子容器结构中,事件会从子容器向上传播至其父容器,但父容器中发布的事件不会向下传播至子容器。 这种设计可以帮助开发者在父容器中集中处理所有的事件,而不必担心事件在多个子容器之间的传播。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

2. PayloadApplicationEvent的使用

 PayloadApplicationEvent是Spring提供的一种特殊事件,用于传递数据(称为"payload")。所以不需要自定义事件,PayloadApplicationEvent可以直接传递任何类型的数据,只需要指定它的类型即可。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

全部代码如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

  • 定义监听器

首先,我们来看怎样定义一个监听器来接收这个事件:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

通用监听器 - 会监听到所有种类的PayloadApplicationEvent:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

package com.example.demo.listener;import org.springframework.context.ApplicationListener;import org.springframework.context.PayloadApplicationEvent;/** * 通用监听器,能监听到所有类型的PayloadApplicationEvent */public class CustomObjectApplicationListener implements ApplicationListener<PayloadApplicationEvent> {        @Override    public void onApplicationEvent(PayloadApplicationEvent event) {        System.out.println("收到PayloadApplicationEvent,数据是:" + event.getPayload());    }}

特定数据类型的监听器 - 只会监听指定类型的数据。例如,如果我们只对字符串数据感兴趣,我们可以如此定义:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

package com.example.demo.listener;import org.springframework.context.ApplicationListener;import org.springframework.context.PayloadApplicationEvent;/** * 特定数据类型的监听器。这个监听器专门监听String类型的PayloadApplicationEvent */public class CustomStringApplicationListener implements ApplicationListener<PayloadApplicationEvent<String>> {        @Override    public void onApplicationEvent(PayloadApplicationEvent<String> event) {        System.out.println("收到了字符串数据:" + event.getPayload());    }}
  • 测试示例

要看这两种监听器如何工作,我们来写一个测试。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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();        // 注册监听器        ctx.addApplicationListener(new CustomObjectApplicationListener());        ctx.addApplicationListener(new CustomStringApplicationListener());        ctx.refresh();        // 发送事件        ctx.publishEvent("Hello, World!");  // 发送一个字符串        ctx.publishEvent(2023);              // 发送一个整数        ctx.publishEvent(new Date());        // 发送一个日期对象    }}

在这个测试中,我们发送了三种类型的数据:一个字符串、一个整数和一个日期。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

执行结果如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

从输出可以看出:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

第一种监听器(通用的)接收到了所有三个事件,因为它不关心数据的具体类型。fXj28资讯网——每日最新资讯28at.com

第二种监听器(字符串专用的)只接收到了字符串类型的事件。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

 虽然PayloadApplicationEvent提供了简化事件监听的能力,但其可能不足以满足特定的业务需求,尤其是当需要更多上下文和数据时。下面是一个使用自定义事件ArticlePublishedEvent的例子。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

全部代码如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

自定义事件: ArticlePublishedEventfXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

这个事件代表了“新文章发布”,附带有文章的标题、作者和发布日期等信息。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

自定义监听器: ArticlePublishedListener

fXj28资讯网——每日最新资讯28at.com

这个监听器专门响应ArticlePublishedEvent,执行特定的业务逻辑,例如通知订阅者、更新搜索索引等。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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());    }}-----------------------------------©著作权归作者所有:来自51CTO博客作者华为云开发者联盟的原创作品,请联系作者获取转载授权,否则将追究法律责任掌握Spring事件监听器的内部逻辑与实现https://blog.51cto.com/u_15214399/8112689

在接收到新文章发布的事件后,监听器ArticlePublishedListener需要执行以下业务逻辑:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

 这样,每次新文章发布的事件被触发时,订阅者都会被通知,搜索引擎的索引将会得到更新,同时相关的统计数据也会得到更新。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

主程序:

fXj28资讯网——每日最新资讯28at.com

模拟文章发布的场景fXj28资讯网——每日最新资讯28at.com

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"));    }}-----------------------------------©著作权归作者所有:来自51CTO博客作者华为云开发者联盟的原创作品,请联系作者获取转载授权,否则将追究法律责任掌握Spring事件监听器的内部逻辑与实现https://blog.51cto.com/u_15214399/8112689

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

运行结果如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 我们可以看到ArticlePublishedEvent比PayloadApplicationEvent具有更多的业务含义和上下文。这样的设计使我们能够更具体地响应和处理特定的业务事件。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 实际上,在企业级应用中,文章发布可能会触发多种不同的后续动作,使用Spring的事件监听器模式可以带来如下优势:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

解耦:事件发布者(即新文章发布功能)不必关心具体的后续处理步骤。它只需发布事件,然后其他感兴趣的监听器会相应地做出响应。这种设计有助于各个功能之间的解耦。fXj28资讯网——每日最新资讯28at.com

可扩展性:如果未来需要为新文章发布添加更多的后续处理,只需添加更多的监听器即可,无需修改原有的业务逻辑。fXj28资讯网——每日最新资讯28at.com

维护性:由于功能之间的解耦,每个功能模块都可以独立维护,这有助于提高代码的可维护性。fXj28资讯网——每日最新资讯28at.com

 Spring为开发者提供了强大的事件监听机制,无论是使用自定义事件还是利用PayloadApplicationEvent进行快速开发,都使我们能够构建一个高度解耦、可扩展且易于维护的系统。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

4. 事件广播原理

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

核心概念:

ApplicationEvent:这是所有Spring事件的超类。用户可以通过继承此类来创建自定义事件。fXj28资讯网——每日最新资讯28at.com

ApplicationListener:这是所有事件监听器的接口。它定义了一个onApplicationEvent方法,用于处理特定类型的事件。fXj28资讯网——每日最新资讯28at.com

ApplicationEventPublisher:这是一个接口,定义了发布事件的方法。ApplicationContext继承了这个接口,因此任何Spring bean都可以发布事件。fXj28资讯网——每日最新资讯28at.com

ApplicationEventMulticaster:这个组件负责将事件广播到所有匹配的监听器。fXj28资讯网——每日最新资讯28at.com

事件发布:

 用户可以通过ApplicationEventPublisher接口或ApplicationContext来发布事件。通常情况下,当我们在Spring bean中需要发布事件时,可以让这个bean实现ApplicationEventPublisherAware接口,这样Spring容器会注入一个事件发布器。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

异步事件:

 从Spring 4.2开始,我们可以轻松地使事件监听器异步化。在Spring 5中,这一功能仍然得到支持。只需要在监听器方法上添加@Async注解并确保启用了异步支持。这使得事件处理可以在单独的线程中执行,不阻塞发布者。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

泛型事件:

 Spring 4.2引入了对泛型事件的支持,这在Spring 5中得到了维护。这意味着监听器现在可以根据事件的泛型类型进行过滤。例如,一个ApplicationListener<ApplicationEvent<String>>将只接收到携带String负载的事件。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

事件的排序:

 监听器可以实现Ordered接口或使用@Order注解来指定事件的执行顺序。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

新的事件类型:

 Spring 5引入了新的事件类型,如ServletRequestHandledEvent,为web请求处理提供更多的钩子。而像ContextRefreshedEvent这样的事件,虽然不是Spring 5新引入的,但它为特定的生命周期回调提供了钩子。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

Reactive事件模型:

 与Spring 5引入的WebFlux一起,还引入了对反应式编程模型的事件监听和发布的支持。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

总结:

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

fXj28资讯网——每日最新资讯28at.com

4.2 发布事件publishEvent源码分析

上图,这里是Spring 5.3.7的源码,下面讲单独抽出来分析fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com


fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

分析:fXj28资讯网——每日最新资讯28at.com

 该方法接受一个ApplicationEvent对象并调用其重载版本publishEvent(Object event, @Nullable ResolvableType eventType),为其传递null作为事件类型。这是为了简化用户使用,用户可以直接传递一个ApplicationEvent对象而无需考虑其具体的类型。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

分析:fXj28资讯网——每日最新资讯28at.com

 与上一个方法类似,但它接受任何Object作为事件,并将其与null的eventType一起传递给核心方法。这增加了灵活性,用户可以发送任何对象作为事件,而不仅仅是ApplicationEvent对象。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {    // 检查事件对象是否为空,确保发布的事件是有意义的    Assert.notNull(event, "Event must not be null");    // 判断传入的事件是否已经是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已经初始化,那么直接通过事件多播器广播事件        this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);    }    // 如果存在父ApplicationContext,则也将事件发布到父容器中    if (this.parent != null) {        if (this.parent instanceof AbstractApplicationContext) {            // 如果父容器是AbstractApplicationContext类型,则带上事件类型进行发布            ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);        } else {            // 否则,只传递事件对象进行发布            this.parent.publishEvent(event);        }    }}-----------------------------------©著作权归作者所有:来自51CTO博客作者华为云开发者联盟的原创作品,请联系作者获取转载授权,否则将追究法律责任掌握Spring事件监听器的内部逻辑与实现https://blog.51cto.com/u_15214399/8112689

这个方法究竟做了什么?fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

事件非空检查:为了确保事件对象不为空,进行了初步的断言检查。这是一个常见的做法,以防止无效的事件被广播。fXj28资讯网——每日最新资讯28at.com

事件类型检查与封装:Spring允许使用任意类型的对象作为事件。如果传入的不是ApplicationEvent的实例,它会使用PayloadApplicationEvent来进行封装。这种设计提供了更大的灵活性。fXj28资讯网——每日最新资讯28at.com

早期事件的处理:在Spring的生命周期中,ApplicationContext可能还没有完全初始化,这时会有一些早期的事件。如果earlyApplicationEvents不为空,这些事件会被添加到此列表中,稍后再广播。fXj28资讯网——每日最新资讯28at.com

事件广播:如果ApplicationContext已初始化,事件会被广播给所有的监听器。这是通过ApplicationEventMulticaster完成的,它是Spring中负责事件广播的核心组件。fXj28资讯网——每日最新资讯28at.com

处理父ApplicationContext:在有些应用中,可以存在父子ApplicationContext。当子容器广播一个事件时,也可以考虑在父容器中广播这个事件。这是为了确保在整个上下文层次结构中的所有感兴趣的监听器都能收到事件。fXj28资讯网——每日最新资讯28at.com

通过这种方式,Spring的事件发布机制确保了事件在不同的上下文和生命周期阶段都能被正确处理和广播。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

上面说到事件广播是ApplicationEventMulticaster完成的,这个是什么?下面来看看fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

4.3 Spring事件广播:从ApplicationEventMulticaster开始

 当我们在Spring中讨论事件,我们实际上是在讨论两件事:事件(即发生的事情)和监听器(即对这些事件感兴趣并作出反应的实体)。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 ApplicationEventMulticaster的主要职责是管理事件监听器并广播事件给这些监听器。我们主要关注SimpleApplicationEventMulticaster,因为这是默认的实现,但请注意,Spring允许替换为自定义的ApplicationEventMulticaster。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

以下是SimpleApplicationEventMulticaster中的相关代码与分析,有兴趣的小伙伴可以自行查看:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {    // 可选的任务执行器,用于异步调用事件监听器。    @Nullable    private Executor taskExecutor;    // 可选的错误处理器,用于处理在广播事件过程中出现的错误。    @Nullable    private ErrorHandler errorHandler;    // 用于记录日志的logger,它是延迟初始化的。    @Nullable    private volatile Log lazyLogger;    // 默认构造函数。    public SimpleApplicationEventMulticaster() {    }    // 带有BeanFactory参数的构造函数,通常用于更复杂的应用上下文配置中。    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {        this.setBeanFactory(beanFactory);    }    // 设置任务执行器。可以是任何Java Executor,比如Spring的SimpleAsyncTaskExecutor或Java的FixedThreadPool。    public void setTaskExecutor(@Nullable Executor taskExecutor) {        this.taskExecutor = taskExecutor;    }    // 获取当前设置的任务执行器。    @Nullable    protected Executor getTaskExecutor() {        return this.taskExecutor;    }    // 设置错误处理器。    public void setErrorHandler(@Nullable ErrorHandler errorHandler) {        this.errorHandler = errorHandler;    }    // 获取当前设置的错误处理器。    @Nullable    protected ErrorHandler getErrorHandler() {        return this.errorHandler;    }    // 这是广播事件的主要方法。它首先解析事件的类型,然后调用具有额外参数的重载方法。    public void multicastEvent(ApplicationEvent event) {        this.multicastEvent(event, this.resolveDefaultEventType(event));    }    // 这个方法是真正执行广播操作的方法。    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType 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);            }        }    }    // 为给定的事件解析默认的事件类型。    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {        return ResolvableType.forInstance(event);    }    // 调用指定的监听器来处理给定的事件,并根据需要处理错误。    protected void invokeListener(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);        }    }    // 直接调用监听器,捕获任何类型不匹配的异常。    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {        try {            listener.onApplicationEvent(event);        } catch (ClassCastException ex) {            // 捕获类型转换异常,并根据需要进行处理。            // 这可以确保如果监听器不能处理特定类型的事件,不会导致整个广播操作失败。            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);            }        }    }    // 根据给定的类型错误消息和事件类来检查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需要作出特别说明:fXj28资讯网——每日最新资讯28at.com

// 这个方法是真正执行广播操作的方法。    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {        // 确定事件类型。        ResolvableType type = (eventType != null) ? eventType : this.resolveDefaultEventType(event);		......		// 获取匹配此事件类型的所有监听器。        Iterator<ApplicationListener<?>> listeners = this.getApplicationListeners(event, type).iterator();        ......    }-----------------------------------©著作权归作者所有:来自51CTO博客作者华为云开发者联盟的原创作品,请联系作者获取转载授权,否则将追究法律责任掌握Spring事件监听器的内部逻辑与实现https://blog.51cto.com/u_15214399/8112689

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

方法第一行得到一个ResolvableType类型的对象,为什么 Spring 选择使用 ResolvableType 而不是直接使用 Java 类型?最主要的原因是 Java 的泛型擦除。 在 Java 中,泛型只存在于编译时,一旦代码被编译,泛型信息就会被擦除,运行时就不能直接获取到泛型的实际类型。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

 为了解决这个问题,Spring 引入了 ResolvableType,一个能够解析泛型类型信息的工具类。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

举个例子:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

假设有如下的类定义:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

public class Sample {    private List<String> names;}

我们可以这样获取 names 字段的泛型类型:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

在 Spring 事件中的使用

fXj28资讯网——每日最新资讯28at.com

 ResolvableType 在 Spring 事件中的应用主要是确定事件的类型和监听器监听的事件类型。当我们发布一个事件:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

 Spring 内部会使用 ResolvableType.forInstance(event) 来获取这个事件的类型。然后,它会找到所有注册的监听器,查看它们监听的事件类型是否与此事件匹配(通过比较 ResolvableType)。匹配的监听器会被调用。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

对于一个监听器:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

Spring 内部会使用 ResolvableType 来解析这个监听器监听的事件类型(在这个例子中是 MyCustomEvent)。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

总之,ResolvableType 在 Spring 中的主要用途是提供了一种方式来解析和操作运行时的泛型类型信息,特别是在事件发布和监听中。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

如果看不清,建议在新标签页中打开图片后放大看fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com


fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

4.5 监听器内部逻辑

再来看看监听器内部逻辑,我们来分析在multicastEvent方法中调用的getApplicationListeners(event, type)来分析下fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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);    // 初始化一个新的监听器检索器为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)))) {        // 创建一个新的检索器        newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();        // 尝试将新检索器添加到缓存中        existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);        // 如果缓存中已经有了一个值(由于并发的原因),则将新检索器设回null        if (existingRetriever != null) {            newRetriever = null;        }    }    // 如果有现有的检索器    if (existingRetriever != null) {        // 尝试从检索器中获取监听器集合        Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();        // 如果结果不为null,则直接返回        if (result != null) {            return result;        }    }    // 如果上述步骤都没有返回,调用retrieveApplicationListeners进行实际的监听器检索    return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);}private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.CachedListenerRetriever retriever) {    // 初始化一个空的监听器列表    List<ApplicationListener<?>> allListeners = new ArrayList();    // 若retriever非null,则初始化集合来保存过滤出来的监听器和Bean名    Set<ApplicationListener<?>> filteredListeners = retriever != null ? new LinkedHashSet() : null;    Set<String> filteredListenerBeans = retriever != null ? new LinkedHashSet() : null;    LinkedHashSet listeners;    LinkedHashSet listenerBeans;    // 同步从defaultRetriever中获取已注册的监听器和其Bean名称    synchronized(this.defaultRetriever) {        listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);        listenerBeans = new LinkedHashSet(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中移除该Bean                    Object 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 = new LinkedHashSet(allListeners);            retriever.applicationListenerBeans = filteredListenerBeans;        } else {            retriever.applicationListeners = filteredListeners;            retriever.applicationListenerBeans = filteredListenerBeans;        }    }    // 返回allListeners作为结果    return allListeners;}-----------------------------------©著作权归作者所有:来自51CTO博客作者华为云开发者联盟的原创作品,请联系作者获取转载授权,否则将追究法律责任掌握Spring事件监听器的内部逻辑与实现https://blog.51cto.com/u_15214399/8112689

监听器内部做了什么?fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

在getApplicationListeners方法中,采用了一种优化检索的缓存机制来提高性能并确保线程安全性。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

具体分析如下:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

首次检查:

fXj28资讯网——每日最新资讯28at.com

AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever =     (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);

这里,我们首先从retrieverCache中检索existingRetriever。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

判断是否需要进入同步代码块:

if (existingRetriever == null && (this.beanClassLoader == null || ...)) {    ...}

fXj28资讯网——每日最新资讯28at.com

如果existingRetriever为空,那么我们可能需要创建一个新的CachedListenerRetriever并放入缓存中。但是,为了确保线程安全性,我们必须在这之前进行进一步的检查。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

双重检查:

在创建新的CachedListenerRetriever之前,我们使用了putIfAbsent方法。这个方法会尝试添加一个新值,但如果该值已存在,它只会返回现有的值。该机制采用了一种缓存优化策略:通过ConcurrentMap的putIfAbsent方法,即使多个线程同时到达这个代码段,也确保只有一个线程能够成功地放入新的值,从而保证线程安全性。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

newRetriever = new AbstractApplicationEventMulticaster.CachedListenerRetriever();existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);

这里的逻辑使用了ConcurrentMap的putIfAbsent方法来确保线程安全性,而没有使用传统的synchronized块。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

所以,我们可以说getApplicationListeners中的这部分逻辑采用了一种优化检索的缓存机制。它利用了并发容器的原子性操作putIfAbsent来保证线程安全,而不是依赖于传统的双重检查锁定模式。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

总体概括一下,对于getApplicationListeners和retrieveApplicationListeners两个方法的功能可以总结为以下三个步骤:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

从默认检索器筛选监听器:

这部分代码直接从defaultRetriever中获取监听器,并检查它们是否支持当前事件。在retrieveApplicationListeners方法中,代码首先从defaultRetriever中获取已经编程式注入的监听器,并检查每个监听器是否支持当前的事件类型。fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);for (ApplicationListener<?> listener : listeners) {    if (this.supportsEvent(listener, eventType, sourceType)) {        ... // 添加到筛选出来的监听器列表    }}

从IOC容器中筛选监听器:

在retrieveApplicationListeners方法中,除了从defaultRetriever中获取已经编程式注入的监听器,代码还会尝试从IOC容器(通过bean名称)获取监听器,并检查它们是否支持当前的事件。fXj28资讯网——每日最新资讯28at.com

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

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

监听器排序:

最后,为确保监听器按照预定的顺序响应事件,筛选出的所有监听器会经过排序。排序基于Spring的@Order注解或Ordered接口,如AnnotationAwareOrderComparator.sort(allListeners)所示fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

AnnotationAwareOrderComparator.sort(allListeners);

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

fXj28资讯网——每日最新资讯28at.com


fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

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

fXj28资讯网——每日最新资讯28at.com


fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

说明:fXj28资讯网——每日最新资讯28at.com

fXj28资讯网——每日最新资讯28at.com

容器和事件广播:

ApplicationContext 是Spring的应用上下文容器。在图中,我们有一个主容器和一个子容器。fXj28资讯网——每日最新资讯28at.com

当我们想发布一个事件时,我们调用 publishEvent 方法。fXj28资讯网——每日最新资讯28at.com

ApplicationEventMulticaster 负责实际地将事件广播到各个监听器。fXj28资讯网——每日最新资讯28at.com

主容器和子容器关系:

在Spring中,可以有多个容器,其中一个是主容器,其他的则是子容器。fXj28资讯网——每日最新资讯28at.com

通常,子容器可以访问主容器中的bean,但反之则不行。但在事件传播的上下文中,子容器发布的事件默认不会在主容器中传播。这一点由 Note1 注释标明。fXj28资讯网——每日最新资讯28at.com

异步处理:

当事件被发布时,它可以被异步地传播到监听器,这取决于是否配置了异步执行器。fXj28资讯网——每日最新资讯28at.com

是否使用异步执行器? 这个决策点说明了基于配置,事件可以同步或异步地传播到监听器。fXj28资讯网——每日最新资讯28at.com

事件生命周期:

在Spring容器的生命周期中,有些事件在容器初始化前触发,这些被称为 early events。这些事件会被缓存起来,直到容器初始化完成。fXj28资讯网——每日最新资讯28at.com

一旦容器初始化完成,这些早期的事件会被处理,并开始处理常规事件。fXj28资讯网——每日最新资讯28at.com

在容器销毁时,也可能触发事件。fXj28资讯网——每日最新资讯28at.com

注意事项 (Note1):fXj28资讯网——每日最新资讯28at.com

这个部分强调了一个特定的行为,即在某些配置下,子容器发布的事件可能也会在主容器中传播,但这并不是默认行为。fXj28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-16286-0.html掌握Spring事件监听器的内部逻辑与实现

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 深入剖析Netflix Ribbon:分布式微服务架构的负载均衡神器

下一篇: WebGPU 入门:绘制一个三角形

标签:
  • 热门焦点
Top