Spring事件监听源码解析

spring事件监听机制离不开容器IOC特性提供的支持,比如容器会自动创建事件发布器,自动识别用户注册的监听器并进行管理,在特定的事件发布后会找到对应的事件监听器并对其监听方法进行回调。Spring帮助用户屏蔽了关于事件监听机制背后的很多细节,使用户可以专注于业务层面进行自定义事件开发。然而我们对内部的实现还是有一些疑问,比如

• 事件发布器ApplicationEventMulticaster是何时被初始化的,初始化过程中都做了什么?
• 注册事件监听器的过程是怎样的,容器怎么识别出它们并进行管理?
• 容器发布事件的流程是怎样的?它如何根据发布的事件找到对应的事件监听器,事件和由该事件触发的监听器之间的匹配规则是怎样的?

初始化事件发布器流程

真正的事件发布器是ApplicationEventMulticaster,它定义在AbstractApplicationContext中,并在ApplicationContext容器启动的时候进行初始化。在容器启动的refrsh()方法中可以找到初始化事件发布器的入口方法,如下图所示:
在这里插入图片描述

/*** Initialize the ApplicationEventMulticaster.* Uses SimpleApplicationEventMulticaster if none defined in the context.* @see org.springframework.context.event.SimpleApplicationEventMulticaster*/protected void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);if (logger.isTraceEnabled()) {logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}}else {this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);if (logger.isTraceEnabled()) {logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");}}}

这里会根据核心容器beanFactory中是否有id为applicationEventMulticaster的bean分两种情况

(1)容器中已有id为applicationEventMulticaster的bean:直接从容器缓存获取或是创建该bean实例,并交由成员变量applicationEventMulticaster保存。当用户自定义了事件发布器并向容器注册时会执行该流程。
(2)容器中不存在applicationEventMulticaster的bean:这是容器默认的执行流程,会创建一个SimpleApplicationEventMulticaster,其仅在实现事件发布器基本功能(管理事件监听器以及发布容器事件)的前提下,增加了可以设置任务执行器Executor和错误处理器ErrorHandler的功能,当设置Executor为线程池时,则会以异步的方式对事件监听器进行回调,而ErrorHandler允许我们在回调方法执行错误时进行自定义处理。默认情况下,这两个变量都为null。

在这里插入图片描述
之后会调用beanFactory.registerSingleton方法将创建的SimpleApplicationEventMulticaster实例注册为容器的单实例bean。
初始化事件发布器总结一句话:由容器实例化用户自定义的事件发布器或者由容器帮我们创建一个简单的事件发布器并交由容器管理。

注册事件监听器流程

注册事件监听器的流程在初始化事件发布器之后,如下图所示:

在这里插入图片描述

/*** Add beans that implement ApplicationListener as listeners.* Doesn't affect other listeners, which can be added without being beans.*/protected void registerListeners() {// 首先注册静态指定的监听器。for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 不要在这里初始化FactoryBeans:我们需要保留所有常规Bean// 未初始化以允许后处理器应用于它们!String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 发布早期应用程序事件Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}

容器事件发布流程

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, ResolvableType)

将给定事件发布给所有侦听器
在这里插入图片描述
前面说,在启动的时候如果没有一个beanName叫做applicationEventMulticaster的ApplicationEventMulticaster,那使用的就是SimpleApplicationEventMulticaster,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。

org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)

@Override 
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { //获取事件类型 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //获取事件发布器内的任务执行器,默认该方法返回null Executor executor = getTaskExecutor(); //遍历所有和事件匹配的事件监听器 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { //异步回调监听方法 executor.execute(() -> invokeListener(listener, event)); } else { //同步回调监听方法 invokeListener(listener, event); } } 
}

如何根据事件类型找到匹配的所有事件监听器?

org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent, ResolvableType)

/*** Return a Collection of ApplicationListeners matching the given* event type. Non-matching listeners get excluded early.* @param event the event to be propagated. Allows for excluding* non-matching listeners early, based on cached matching information.* @param eventType the event type* @return a Collection of ApplicationListeners* @see org.springframework.context.ApplicationListener*/protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {// 获取事件中的事件源对象Object source = event.getSource();// 获取事件源类型Class<?> sourceType = (source != null ? source.getClass() : null);// 以事件类型和事件源类型为参数构建一个cacheKey ,用于从缓存map中获取与之匹配的监听器列表ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);// 根据cacheKey从缓存中获取CachedListenerRetrieverListenerRetriever retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {return retriever.getApplicationListeners();}if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {// Fully synchronized building and caching of a ListenerRetrieversynchronized (this.retrievalMutex) {retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {return retriever.getApplicationListeners();}retriever = new ListenerRetriever(true);// 不存在就检索给定事件和源类型的应用程序侦听器,并放到缓存Collection<ApplicationListener<?>> listeners =retrieveApplicationListeners(eventType, sourceType, retriever);this.retrieverCache.put(cacheKey, retriever);return listeners;}}else {// No ListenerRetriever caching -> no synchronization necessaryreturn retrieveApplicationListeners(eventType, sourceType, null);}}

如果事件时第一次发布,会遍历所有的事件监听器,并根据事件类型和事件源类型进行匹配:

org.springframework.context.event.AbstractApplicationEventMulticaster#retrieveApplicationListeners

/*** Actually retrieve the application listeners for the given event and source type.* @param eventType the event type* @param sourceType the event source type* @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)* @return the pre-filtered list of application listeners for the given event and source type*/private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {// 存放监听器的列表List<ApplicationListener<?>> allListeners = new ArrayList<>();Set<ApplicationListener<?>> listeners;Set<String> listenerBeans;synchronized (this.retrievalMutex) {listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 添加以编程方式注册的侦听器,// 包括来自ApplicationListenerDetector的侦听器(单例bean和内部bean)。for (ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {retriever.applicationListeners.add(listener);}allListeners.add(listener);}}// 按bean名称添加侦听器,这可能与上面通过编程注册的侦听器重叠,// 但这里可能有额外的元数据。if (!listenerBeans.isEmpty()) {ConfigurableBeanFactory beanFactory = getBeanFactory();for (String listenerBeanName : listenerBeans) {try {if (supportsEvent(beanFactory, listenerBeanName, eventType)) {ApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class);if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {if (beanFactory.isSingleton(listenerBeanName)) {retriever.applicationListeners.add(listener);}else {retriever.applicationListenerBeans.add(listenerBeanName);}}allListeners.add(listener);}}else {// 删除最初来自ApplicationListenerDetector的不匹配侦听器,// 可能会被上面额外的BeanDefinition元数据(例如工厂方法泛型)排除。Object listener = beanFactory.getSingleton(listenerBeanName);if (retriever != null) {retriever.applicationListeners.remove(listener);}allListeners.remove(listener);}}catch (NoSuchBeanDefinitionException ex) {// 单一侦听器实例(没有支持bean定义)消失-可能在销毁阶段中期}}}//对匹配的监听器列表进行排序AnnotationAwareOrderComparator.sort(allListeners);if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {retriever.applicationListeners.clear();retriever.applicationListeners.addAll(allListeners);}return allListeners;}

容器事件发布的整个流程,可以总结如下
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Selenium的使用:WEB功能测试

Selenium是ThrougthWorks公司一个强大的开源WEB功能测试工具系列&#xff0c;本系统包括多款软件 Selenium语言简单&#xff0c;用(Command,target,value)三种元素组成一个行为&#xff0c;并且有协助录制脚本工具&#xff0c;但Selenese有一些严格的限制&#xff1a; …

FFmpeg5.0源码阅读——VideoToobox硬件解码

摘要&#xff1a;本文描述了FFmpeg中videotoobox解码器如何进行解码工作&#xff0c;如何将一个编码的码流解码为最终的裸流。   关键字&#xff1a;videotoobox,decoder,ffmpeg   VideoToolbox 是一个低级框架&#xff0c;提供对硬件编码器和解码器的直接访问。 它提供视频…

C语言:字符函数和字符串函数

往期文章 C语言&#xff1a;初识C语言C语言&#xff1a;分支语句和循环语句C语言&#xff1a;函数C语言&#xff1a;数组C语言&#xff1a;操作符详解C语言&#xff1a;指针详解C语言&#xff1a;结构体C语言&#xff1a;数据的存储 目录 往期文章前言1. 函数介绍1.1 strlen1.…

速通蓝桥杯嵌入式省一教程:(五)用按键和屏幕实现嵌入式交互系统

一个完整的嵌入式系统&#xff0c;包括任务执行部分和人机交互部分。在前四节中&#xff0c;我们已经讲解了LED、LCD和按键&#xff0c;用这三者就能够实现一个人机交互系统&#xff0c;也即搭建整个嵌入式系统的框架。在后续&#xff0c;只要将各个功能加入到这个交互系统中&a…

【数据库系统】--【2】DBMS架构

DBMS架构 01DBMS架构概述02 DBMS的物理架构03 DBMS的运行和数据架构DBMS的运行架构DBMS的数据架构PostgreSQL的体系结构RMDB的运行架构 04DBMS的逻辑和开发架构DBMS的层次结构DBMS的开发架构DBMS的代码架构 05小结 01DBMS架构概述 02 DBMS的物理架构 数据库系统的体系结构 数据…

pytorch 42 C#使用onnxruntime部署内置nms的yolov8模型

在进行目标检测部署时,通常需要自行编码实现对模型预测结果的解码及与预测结果的nms操作。所幸现在的各种部署框架对算子的支持更为灵活,可以在模型内实现预测结果的解码,但仍然需要自行编码实现对预测结果的nms操作。其实在onnx opset===11版本以后,其已支持将nms操作嵌入…

Maven - 统一构建规范:Maven 插件管理最佳实践

文章目录 Available Plugins开源项目中的使用插件介绍maven-jar-pluginmaven-assembly-pluginmaven-shade-pluginShade 插件 - 标签artifactSetrelocationsfilters 完整配置 Available Plugins https://maven.apache.org/plugins/index.html Maven 是一个开源的软件构建工具&…

使用yolov5进行安全帽检测填坑指南

参考项目 c​​​​​​​​​​​​​​GitHub - PeterH0323/Smart_Construction: Base on YOLOv5 Head Person Helmet Detection on Construction Sites&#xff0c;基于目标检测工地安全帽和禁入危险区域识别系统&#xff0c;&#x1f680;&#x1f606;附 YOLOv5 训练自己的…

visual studio 2022配置

前提&#xff1a;我linux c 开发 一直在使用vscode 更新了个版本突然代码中的查找所用引用和变量修改名称不能用了&#xff0c;尝试了重新配置clang vc都不行&#xff0c;估计是插件问题&#xff0c;一怒之下改用visual studio 2022 为了同步2个IDE之间的差别&#xff0c;目前…

知识继承概述

文章目录 知识继承第一章 知识继承概述1.背景介绍第一页 背景第二页 大模型训练成本示例第三页 知识继承的动机 2.知识继承的主要方法 第二章 基于知识蒸馏的知识继承预页 方法概览 1.知识蒸馏概述第一页 知识蒸馏概述第二页 知识蒸馏第三页 什么是知识第四页 知识蒸馏的核心目…

LeetCode_Java_2236. 判断根结点是否等于子结点之和

2236. 判断根结点是否等于子结点之和 给你一个 二叉树 的根结点 root&#xff0c;该二叉树由恰好 3 个结点组成&#xff1a;根结点、左子结点和右子结点。 如果根结点值等于两个子结点值之和&#xff0c;返回 true &#xff0c;否则返回 false 。 示例1 输入&#xff1a;roo…

每天一道leetcode:剑指 Offer 34. 二叉树中和为某一值的路径(中等图论深度优先遍历递归)

今日份题目&#xff1a; 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例1 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSu…

SpringBoot中优雅的实现隐私数据脱敏(提供Gitee源码)

前言&#xff1a;在实际项目开发中&#xff0c;可能会对一些用户的隐私信息进行脱敏操作&#xff0c;传统的方式很多都是用replace方法进行手动替换&#xff0c;这样会由很多冗余的代码并且后续也不好维护&#xff0c;本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去…

深入解析 Axios Blob 的使用方法及技巧

在 Web 开发中&#xff0c;处理文件传输是一个常见的需求。Blob&#xff08;二进制对象&#xff09;是一种表示二进制数据的方式&#xff0c;常用于处理文件和多媒体数据。本文将介绍如何使用 Axios 和 Blob 来处理文件传输。 Axios Blob 概念 在开始之前&#xff0c;让我们先…

Redis中常见的缓存穿透、缓存击穿、缓存雪崩、缓存预热解决方案

文章目录 一、缓存穿透1. 什么是缓存穿透2. 解决方案2.1 无效的key存放到Redis2.2 引入布隆过滤器2.3 如何选择&#xff1a; 二、缓存击穿1. 什么是缓存击穿2. 解决方案 三、缓存雪崩1. 什么是缓存雪崩2. 解决方案2.1 均匀过期2.2 热点数据缓存永远不过期2.3 采取限流降级的策略…

[ MySQL ] — 常见函数的使用

目录 日期函数 current_date — 获取当前日期 current_time — 获取当前时间 current_timestamp — 获取当前时间戳 date — 获取参数的日期部分 ​编辑 date_add — 在日期或时间的基础上进行增加 date_sub — 在日期或时间的基础上进行减少 datediff — 计算两个日期相差…

mysql主从复制最简单环境搭建(一主一从)

提示&#xff1a;前面有相应的文章利用不同方式进行的主从配置 文章目录 前言一、概述二、主从复制的优点三、原理四、搭建五、主库配置六、从库配置七、测试 前言 一、概述 主从复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上…

听GPT 讲Prometheus源代码--rules

Prometheus的rules目录主要包含规则引擎和管理规则的文件: engine.go 该文件定义了规则引擎的接口和主要结构,包括Rule,Record,RuleGroup等。它提供了规则的加载、匹配、评估和结果记录的功能。 api.go 定义了用于管理和查询规则的RESTful API,包括获取、添加、删除规则等方法。…

C国演义 [第十二章]

第十二章 打家劫舍题目理解步骤dp数组递推公式初始化遍历顺序 代码 打家劫舍II题目理解步骤递推公式初始化遍历顺序 代码 打家劫舍 力扣链接 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋…

『SpringBoot 源码分析』run() 方法执行流程:(1)初始化 SpringApplication 、上下文环境、应用上下文

『SpringBoot 源码分析』run() 方法执行流程&#xff1a;&#xff08;1&#xff09;初始化 SpringApplication 、上下文环境、应用上下文 基于 2.2.9.RELEASE问题&#xff1a;当方法进行了注释标记之后&#xff0c;springboot 又是怎么注入到容器中并创建类呢&#xff1f; 首…