您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

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

时间:2023-11-01 13:40:03  来源:51CTO  作者:华为云开发者联盟

1. 事件的层次传播

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

 为了探讨这个问题,我们创建了一个名为HierarchicalEventPropagationEvent的事件类和一个对应的监听器HierarchicalEventPropagationListener。

全部代码如下:

package com.example.demo.event;

import org.springframework.context.ApplicationEvent;

// 事件类
public class HierarchicalEventPropagationEvent extends ApplicationEvent {

    private String message;

    public HierarchicalEventPropagationEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

相应地,为 HierarchicalEventPropagationEvent 定义一个监听器HierarchicalEventPropagationListener :

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

}

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

 请注意,首先需要刷新主容器,然后刷新子容器。否则会出现异常:Exception in thread "mAIn" JAVA.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@somehashcode

主程序如下:

ackage com.example.demo;

import com.example.demo.event.HierarchicalEventPropagationEvent;
import com.example.demo.listener.HierarchicalEventPropagationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {

    public static void main(String[] args) {
        // 创建父容器,注册监听器
        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);
    }
}

运行结果

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

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

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

 

2. PayloadApplicationEvent的使用

 PayloadApplicationEvent是Spring提供的一种特殊事件,用于传递数据(称为"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());        // 发送一个日期对象
    }
}

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

执行结果如下:

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

从输出可以看出:

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

第二种监听器(字符串专用的)只接收到了字符串类型的事件。

 

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

运行结果如下:

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

 

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

 

 实际上,在企业级应用中,文章发布可能会触发多种不同的后续动作,使用Spring的事件监听器模式可以带来如下优势:

 

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

可扩展性:如果未来需要为新文章发布添加更多的后续处理,只需添加更多的监听器即可,无需修改原有的业务逻辑。

维护性:由于功能之间的解耦,每个功能模块都可以独立维护,这有助于提高代码的可维护性。

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

 

4. 事件广播原理

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

核心概念:

ApplicationEvent:这是所有Spring事件的超类。用户可以通过继承此类来创建自定义事件。

ApplicationListener:这是所有事件监听器的接口。它定义了一个onApplicationEvent方法,用于处理特定类型的事件。

ApplicationEventPublisher:这是一个接口,定义了发布事件的方法。ApplicationContext继承了这个接口,因此任何Spring bean都可以发布事件。

ApplicationEventMulticaster:这个组件负责将事件广播到所有匹配的监听器。

事件发布:

 用户可以通过ApplicationEventPublisher接口或ApplicationContext来发布事件。通常情况下,当我们在Spring bean中需要发布事件时,可以让这个bean实现ApplicationEventPublisherAware接口,这样Spring容器会注入一个事件发布器。

 

异步事件:

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

 

泛型事件:

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

 

事件的排序:

 监听器可以实现Ordered接口或使用@Order注解来指定事件的执行顺序。

 

新的事件类型:

 Spring 5引入了新的事件类型,如ServletRequestHandledEvent,为web请求处理提供更多的钩子。而像ContextRefreshedEvent这样的事件,虽然不是Spring 5新引入的,但它为特定的生命周期回调提供了钩子。

 

Reactive事件模型:

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

 

总结:

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

 

4.2 发布事件publishEvent源码分析

上图,这里是Spring 5.3.7的源码,下面讲单独抽出来分析

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

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

分析:

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

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

分析:

 与上一个方法类似,但它接受任何Object作为事件,并将其与null的eventType一起传递给核心方法。这增加了灵活性,用户可以发送任何对象作为事件,而不仅仅是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);
        }
    }
}

这个方法究竟做了什么?

事件非空检查:为了确保事件对象不为空,进行了初步的断言检查。这是一个常见的做法,以防止无效的事件被广播。

事件类型检查与封装:Spring允许使用任意类型的对象作为事件。如果传入的不是ApplicationEvent的实例,它会使用PayloadApplicationEvent来进行封装。这种设计提供了更大的灵活性。

早期事件的处理:在Spring的生命周期中,ApplicationContext可能还没有完全初始化,这时会有一些早期的事件。如果earlyApplicationEvents不为空,这些事件会被添加到此列表中,稍后再广播。

事件广播:如果ApplicationContext已初始化,事件会被广播给所有的监听器。这是通过ApplicationEventMulticaster完成的,它是Spring中负责事件广播的核心组件。

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

 

方法第一行得到一个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 事件中的使用

 

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

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)。

 

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

 

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

如果看不清,建议在新标签页中打开图片后放大看

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

监听器内部做了什么?

 

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

 

具体分析如下:

 

首次检查:

 

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

这里,我们首先从retrieverCache中检索existingRetriever。

 

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

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

 

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

 

双重检查:

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

 

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

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

 

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

 

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

 

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

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

 

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

从IOC容器中筛选监听器:

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

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

 

 

监听器排序:

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

 

 

AnnotationAwareOrderComparator.sort(allListeners);

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

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

 

 

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

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

 

 

说明:

 

容器和事件广播:

ApplicationContext 是Spring的应用上下文容器。在图中,我们有一个主容器和一个子容器。

当我们想发布一个事件时,我们调用 publishEvent 方法。

ApplicationEventMulticaster 负责实际地将事件广播到各个监听器。

主容器和子容器关系:

在Spring中,可以有多个容器,其中一个是主容器,其他的则是子容器。

通常,子容器可以访问主容器中的bean,但反之则不行。但在事件传播的上下文中,子容器发布的事件默认不会在主容器中传播。这一点由 Note1 注释标明。

异步处理:

当事件被发布时,它可以被异步地传播到监听器,这取决于是否配置了异步执行器。

是否使用异步执行器? 这个决策点说明了基于配置,事件可以同步或异步地传播到监听器。

事件生命周期:

在Spring容器的生命周期中,有些事件在容器初始化前触发,这些被称为 early events。这些事件会被缓存起来,直到容器初始化完成。

一旦容器初始化完成,这些早期的事件会被处理,并开始处理常规事件。

在容器销毁时,也可能触发事件。

注意事项 (Note1):

这个部分强调了一个特定的行为,即在某些配置下,子容器发布的事件可能也会在主容器中传播,但这并不是默认行为。



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(52)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(9)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(93)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(90)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(131)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(52)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(46)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(67)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(85)  评论:(0)  加入收藏
站内最新
站内热门
站内头条