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

news2025/1/11 11:42:12

文章目录

  • 1. 事件的层次传播
  • 2. PayloadApplicationEvent的使用
  • 3. 为什么选择自定义事件?
  • 4. 事件广播原理
    • 4.1 Spring 5.x的事件模型概述
    • 4.2 发布事件publishEvent源码分析
    • 4.3 Spring事件广播:从ApplicationEventMulticaster开始
    • 4.4 Spring事件发布与处理流程图
    • 4.5 监听器内部逻辑
    • 4.6 Spring事件监听器检索流程图
  • 5. Spring事件传播、异步处理等机制的详细图示

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

1. 事件的层次传播

  Spring中的ApplicationContext(上下文容器)可以有父子关系。当我们在这样的环境中工作时,经常会有这样的疑问:如果顶层容器发布了事件,那么子级容器能否接收到?反过来,如果子级容器发布事件,顶层容器能否接收到呢?我们接下来将探究这个特性。

全部代码如下:

  定义一个EventCategory枚举,该枚举用于区分不同的事件类型。比如,当一个文章被发布或用户登录时,我们可能想要触发不同的事件。有了这个枚举,我们可以轻松地扩展更多的事件类型,保证每种事件都有一个清晰的标识这增加了事件分类的灵活性和扩展性。

package com.example.demo.enums;

/**
 * EventCategory 是一个枚举类型,用于定义不同的事件类型。
 */
public enum EventCategory {
    ARTICLE_PUBLISHED, // 表示文章发布事件
    USER_LOGIN // 表示用户登录事件
}

接着定义事件ArticlePublishedEvent,只需继承 ApplicationEvent即可。

  当文章被发布时,我们想要触发一个特定的事件,这就是ArticlePublishedEvent的角色。它继承了SpringApplicationEvent,这使得它可以被Spring的事件监听器捕获。除了基本的事件信息,如标题、作者和发布日期,它还包含一个EventCategory属性,用于表示这是一个文章发布事件。

package com.example.demo.event;

import com.example.demo.enums.EventCategory;
import org.springframework.context.ApplicationEvent;

/**
 * ArticlePublishedEvent 是一个事件类,代表了文章发布的事件。
 * 当文章被发布时,这个事件会被触发并传递到注册的监听器。
 */
public class ArticlePublishedEvent extends ApplicationEvent {
    private String title;
    private String author;
    private String publishedDate;
    private EventCategory category;
    
    /**
     * 构造方法。
     *
     * @param source         事件的源对象
     * @param title          文章的标题
     * @param author         文章的作者
     * @param 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;
    }

    public EventCategory getCategory() {
        return category;
    }

    public void setCategory(EventCategory category) {
        this.category = category;
    }
}

相应地,为 ArticlePublishedEvent 定义一个监听器ArticlePublishedListener

package com.example.demo.listener;

import com.example.demo.enums.EventCategory;
import com.example.demo.event.ArticlePublishedEvent;
import org.springframework.context.ApplicationListener;

/**
 * ArticlePublishedListener 是一个监听器,它监听 ArticlePublishedEvent 事件。
 * 当这个事件被触发时,onApplicationEvent 方法会被调用。
 */
public class ArticlePublishedListener implements ApplicationListener<ArticlePublishedEvent> {

    private final String listenerId;
    
    /**
     * 构造方法。
     *
     * @param listenerId  监听器的唯一标识
     */
    public ArticlePublishedListener(String listenerId) {
        this.listenerId = listenerId;
    }

    @Override
    public void onApplicationEvent(ArticlePublishedEvent event) {
        if (event.getCategory() == EventCategory.ARTICLE_PUBLISHED) {
            System.out.println("Listener [" + listenerId + "] received an article published event: " + event.getTitle());
        } else {
            System.out.println("Listener [" + listenerId + "] received an event for article: " + event.getTitle());
        }
    }
}

  为了测试继承机制,我们需要构建主子容器。因此,我们会创建两个 ApplicationContext 并为每个容器注册一个 ArticlePublishedListener,初始化容器后就可以发布事件了,然后分别使用主从容器发布事件。

  请注意,首先需要刷新主容器,然后刷新子容器。否则会出现异常: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.enums.EventCategory;
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 parent ApplicationContext
        AnnotationConfigApplicationContext parentCtx = new AnnotationConfigApplicationContext();
        parentCtx.addApplicationListener(new ArticlePublishedListener("Parent Listener"));
        parentCtx.refresh();

        // Initialize child ApplicationContext
        AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
        childCtx.setParent(parentCtx);
        childCtx.addApplicationListener(new ArticlePublishedListener("Child Listener"));
        childCtx.refresh();

        // Simulate publishing an article from parent
        System.out.println("Publishing event from parent context...");
        ArticlePublishedEvent articlePublishedEvent = new ArticlePublishedEvent(parentCtx, "Spring Events in Parent", "John Doe", "2023-09-15");
        articlePublishedEvent.setCategory(EventCategory.ARTICLE_PUBLISHED);
        parentCtx.publishEvent(articlePublishedEvent);

        // Simulate publishing an article from child
        System.out.println("\nPublishing event from child context...");
        childCtx.publishEvent(new ArticlePublishedEvent(childCtx, "Spring Events in Child", "John Doe", "2023-09-15"));

    }
}

运行结果

在这里插入图片描述

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

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

2. PayloadApplicationEvent的使用

  PayloadApplicationEventSpring提供的一种特殊事件,用于传递数据(称为"payload")。所以不需要自定义事件,PayloadApplicationEvent可以直接传递任何类型的数据,只需要指定它的类型即可。

  • 定义监听器

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

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

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

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

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());
    }
}
  • 测试示例

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

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());        // 发送一个日期对象
    }
}

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

执行结果如下:

在这里插入图片描述

从输出可以看出:

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

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

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

全部代码如下:

自定义事件: ArticlePublishedEvent

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

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

这个监听器专门响应ArticlePublishedEvent,执行特定的业务逻辑,例如通知订阅者、更新搜索索引等。

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

在接收到新文章发布的事件后,监听器ArticlePublishedListener需要执行以下业务逻辑:

  • 通知所有订阅新文章通知的用户。

  • 将新文章的标题、作者和发布日期添加到搜索引擎的索引中,以便用户可以搜索到这篇新文章。

  • 更新统计信息,例如总文章数、最近发布的文章等。

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

主程序

模拟文章发布的场景

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

运行结果如下:

在这里插入图片描述

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

  实际上,在企业级应用中,文章发布可能会触发多种不同的后续动作,使用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的源码,下面讲单独抽出来分析

在这里插入图片描述

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

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

这个方法究竟做了什么?

  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中的相关代码与分析,有兴趣的小伙伴可以自行查看:

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需要作出特别说明:

    // 这个方法是真正执行广播操作的方法。
    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType 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,一个能够解析泛型类型信息的工具类

举个例子:

假设有如下的类定义:

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 事件中的应用主要是确定事件的类型和监听器监听的事件类型。当我们发布一个事件:

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

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

对于一个监听器:

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

Spring 内部会使用 ResolvableType 来解析这个监听器监听的事件类型(在这个例子中是 MyCustomEvent)。

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

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

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

4.5 监听器内部逻辑

再来看看监听器内部逻辑,我们来分析在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);
    // 初始化一个新的监听器检索器为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;
}

监听器内部做了什么?

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 = new AbstractApplicationEventMulticaster.CachedListenerRetriever();
existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);

这里的逻辑使用了ConcurrentMapputIfAbsent方法来确保线程安全性,而没有使用传统的synchronized块。

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

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

  1. 从默认检索器筛选监听器
    这部分代码直接从defaultRetriever中获取监听器,并检查它们是否支持当前事件。在retrieveApplicationListeners方法中,代码首先从defaultRetriever中获取已经编程式注入的监听器,并检查每个监听器是否支持当前的事件类型。
listeners = new LinkedHashSet(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-----------------------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1128285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录 Day27 贪心02上 LeetCode T122 买卖股票的最佳时机 II

LeetCode T122 买卖股票的最佳时机II 题目链接:122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 题目思路: 这题的基本思路还是使用贪心算法,有人可能还在思考啥是贪心算法,这个算法就是你提出一个思想,且找不到明显的反例,这个思路就可以一试,这道题的局…

腾讯云2023年双11云服务器优惠价格表

腾讯云2023年双11大促优惠活动已经拉开序幕&#xff0c;腾讯云推出了一系列云服务器的优惠活动&#xff0c;下面给大家整理分享腾讯云双11云服务器优惠价格表&#xff0c;让大家轻松了解各种云服务器实例的折扣价格和配置信息。 一、腾讯云双十一活动入口 活动入口&#xff1a;…

分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测

分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测 目录 分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据…

墨西哥专线:国产商品迅速开拓墨西哥市场的基础

随着全球贸易的不断发展&#xff0c;越来越多的中国企业开始将目光投向海外市场。墨西哥作为北美洲的重要国家&#xff0c;拥有庞大的消费市场和广阔的发展空间&#xff0c;对于中国企业来说&#xff0c;无疑是一个极具潜力的市场。然而&#xff0c;如何让国产商品在这个市场上…

【Docker】Docker-Compose内置DNS负载均衡失效问题

Docker Compose实现负载均衡 还是对前面的例子docker-compose.yml稍微修改&#xff1a; version: "3.8"services:flask-demo:build:context: .dockerfile: Dockerfileimage: flask-demo:latestenvironment:- REDIS_HOSTredis-server- REDIS_PASS${REDIS_PASS}healt…

有没有PC端的配音软件推荐?(免下载)

配音软件还是电脑上使用最方便&#xff0c;而且电脑上可以使用的配音软件也非常多。只是你平时使用的不多&#xff0c;所有想用的时候才会找不到&#xff0c;对于经常使用配音软件的人来说&#xff0c;那真的太多了。今天给大家推荐一个免下载的配音网站&#xff0c;微信扫码即…

如何解决网站被攻击的问题?

在数字时代&#xff0c;网站安全问题日益突出&#xff0c;网络攻击的形式不断演进&#xff0c;因此&#xff0c;保护网站免受威胁至关重要。本文将探讨如何解决网站被攻击的问题&#xff0c;分析未来的网络攻击形式&#xff0c;并提供一些通俗易懂的建议&#xff0c;以加强网站…

【机器学习合集】泛化与正则化合集 ->(个人学习记录笔记)

文章目录 泛化与正则化1. 泛化(generalization)2. 正则化方法2.1 显式正则化方法显式正则化方法对比提前终止模型的训练多个模型集成Dropout技术 2.2 参数正则化方法2.3 隐式正则化方法方法对比 泛化与正则化 1. 泛化(generalization) 泛化不好可能带来的问题 模型性能不稳定容…

安全响应中心 — 垃圾邮件事件报告(10.13)

2023年10月 第二周 一. 样本概况 ✅ 案例1&#xff1a;DocuSign钓鱼 本周收到一封看似来自 DocuSign&#xff08;DocuSign 是一种在企业环境中广泛使用的电子协议管理平台&#xff09;的网络钓鱼电子邮件反馈。 如下图所示&#xff1a; 以上样本内容大体是说XX发送了一份文…

【postman】postman的使用与postman汉化

postman的使用 Postman 是一个接口测试工具软件&#xff0c;可以帮助开发人员管理测试接口。 官网&#xff1a;Postman API Platform psotman环境 首先import的或则new 创建一个环境 Variable 变量名 Type 类型 Initial value 初始值 C…

ubuntu vbox 5.2 资源 virtualbox-dkms

各种 linux 包 https://pkgs.org/search/?qpython3.6 配置 python 默认版本 查看已安装python $ u82:~/Py_demo$ ls /usr/bin/python* /usr/bin/python2 /usr/bin/python2.7 /usr/bin/python3 /usr/bin/python3.8设置默认版本 $ u82:~/Py_demo$ sudo update-alternatives…

VR智慧景区,为游客开启智慧旅游新时代

近年来&#xff0c;文旅部加强了5G、VR虚拟技术等在文旅产业行业的运用&#xff0c;随着科技的不断发展&#xff0c;VR技术的运用越来越广泛&#xff0c;VR智慧景区作为一种全新的旅游方式&#xff0c;也渐渐的受到了人们广泛的关注&#xff0c;它可以让人们足不出户就欣赏到各…

数字IC后端面试题目汇总含解析,ICer上岸必备!

大家都知道&#xff0c;面试的表现会对于个人职业发展的重要性&#xff0c;不仅能决定是否录用&#xff0c;还会影响到后期的谈薪&#xff0c;所以面试前一定要做好充分的准备。 今天IC修真院为大家带来了数字IC后端的面试题目&#xff0c;希望大家能够用得上。 面试题目&…

javaEE -5(8000字详解多线程)

一&#xff1a;JUC(java.util.concurrent) 的常见类 1.1 ReentrantLock 可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全&#xff0c;ReentrantLock 也是可重入锁. “Reentrant” 这个单词的原意就是 “可重入” ReentrantLock 的用法&#xf…

学习笔记二十四:K8S四层代理Service

K8S四层代理Service 四层负载均衡Service&#xff1a;概念、原理解读为什么要有ServiceService概述Service工作原理kubernetes集群中有三类IP地址 查看定义Service资源需要的字段有哪些Service的四种类型Service的端口 创建Service&#xff1a;type类型是ClusterIP创建Service&…

保护公司数据安全的措施

保护公司数据安全的措施 互联网时代&#xff0c;数据安全对企业而言是非常重要的&#xff0c;数据是每个组织的命脉&#xff0c;保护好数据安全企业才能稳定长久的发展下去&#xff0c;很多企业就是因为不重视数据安全防护&#xff0c;导致数据泄露从而遭受了很大的损失。因此…

Java入门讲解(1)---让你瞬间明白如何安装jdk

博主有话说&#xff1a;学习这个东西一定要持之以恒&#xff01;&#xff01;&#xff01;博主之前因为点事情半个月没学习&#xff0c;重新来过时&#xff0c;发现自己错过好多知识&#xff0c;正在一点一点往回补&#xff0c;博客也会陆续开始更新&#xff0c;希望大家多多支…

80.每日一练:移除元素(力扣)

问题描述 代码解决以及思想 解法一 class Solution { public:int removeElement(vector<int>& nums, int val) {int len 0; // 初始化一个用于记录非目标值个数的变量// 创建一个迭代器 it&#xff0c;指向 nums 的开头vector<int>::iterator it nums.beg…

【C语言】字符串+内存函数的介绍

&#x1f388;个人主页&#xff1a;.满船清梦压星河_-CSDN博客 &#x1f302;c/c领域新星创作者 &#x1f389;欢迎&#x1f44d;点赞✍评论❤️收藏 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xf…