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;}
容器事件发布的整个流程,可以总结如下:

相关文章:
Spring事件监听源码解析
spring事件监听机制离不开容器IOC特性提供的支持,比如容器会自动创建事件发布器,自动识别用户注册的监听器并进行管理,在特定的事件发布后会找到对应的事件监听器并对其监听方法进行回调。Spring帮助用户屏蔽了关于事件监听机制背后的很多细节…...
Cpp学习——list的模拟实现
目录 一,实现list所需要包含的三个类 二,三个类的实现 1.list_node 2.list类 3.iterator_list类 三,功能实现 1.list类里的push_back() 2.iterator类里的运算符重载 3,list类里面的功能函数 1.insert(ÿ…...
工具推荐:Chat2DB一款开源免费的多数据库客户端工具
文章首发地址 Chat2DB是一款开源免费的多数据库客户端工具,适用于Windows和Mac操作系统,可在本地安装使用,也可以部署到服务器端并通过Web页面进行访问。 相较于传统的数据库客户端软件如Navicat、DBeaver,Chat2DB具备了与AIGC…...
C语言刷题指南(二)
📙作者简介: 清水加冰,目前大二在读,正在学习C/C、Python、操作系统、数据库等。 📘相关专栏:C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 👍…...
[C++11]
文章目录 1. 自动类型推导1.1 auto1.1.1 推导规则1.1.2 auto的限制1.1.3 auto的应用1.1.4 范围for 1.2 decltype1.2.1 推导规则1.2.2 decltype的应用 1.3 返回类型后置 2.可调用对象包装器、绑定器2.1 可调用对象包装器2.1.1 基本用法2.1.2 作为回调函数使用 2.2 绑定器 3. usi…...
【MySQL系列】--初识数据库
💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …...
Unity导入google.protobuf失败,无法找到google命名空间
问题: 1.刚开始把protobuf的文件夹直接从其他项目里(unity2021)里复制到unity(2020)版本,当时报错protobuf.dll的依赖项system.memory版本不对。 2.没有使用原来的protobuf文件了。使用vs2019的NuGet管理包来下载Google.Protobuf ,仍然报错找…...
使用IDM下载视频出现“由于法律原因,IDM无法下载...
一、问题描述 由于法律原因,IDM无法下载..,如图: 二、原因分析 下载该IDM抓取的M3U8文件,查看其中的内容发现 : #EXT-X-KEY 字段已经写明了加密方式是AES-128,包含一个URI和IV值 #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:8 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:…...
pointnet C++推理部署--tensorrt框架
classification 如上图所示,由于直接export出的onnx文件有两个输出节点,不方便处理,所以编写脚本删除不需要的输出节点193: import onnxonnx_model onnx.load("cls.onnx") graph onnx_model.graphinputs graph.inpu…...
34.Netty源码之Netty如何处理网络请求
highlight: arduino-light 通过前面两节源码课程的学习,我们知道 Netty 在服务端启动时会为创建 NioServerSocketChannel,当客户端新连接接入时又会创建 NioSocketChannel,不管是服务端还是客户端 Channel,在创建时都会初始化自己…...
vscode 安装勾选项解释
1、通过code 打开“操作添加到windows资源管理器文件上下文菜单 :把这个两个勾选上,可以对文件使用鼠标右键,选择VSCode 打开。 2、将code注册为受支持的文件类型的编辑器:不建议勾选,这样会默认使用VSCode打开支持的相…...
Spring 6.0官方文档示例(24): replace-method的用法
一、原始bean定义 package cn.edu.tju.study.service.anno.domain;public class MyValueCalculator {public String computeValue(String input) {return "you inputted: " input;}// some other methods... }二、replace bean定义 package cn.edu.tju.study.serv…...
自然语言处理从入门到应用——LangChain:记忆(Memory)-[聊天消息记录]
分类目录:《自然语言处理从入门到应用》总目录 Cassandra聊天消息记录 Cassandra是一种分布式数据库,非常适合存储大量数据,是存储聊天消息历史的良好选择,因为它易于扩展,能够处理大量写入操作。 # List of contact…...
Python web实战之细说 Django 的单元测试
关键词: Python Web 开发、Django、单元测试、测试驱动开发、TDD、测试框架、持续集成、自动化测试 大家好,今天,我将带领大家进入 Python Web 开发的新世界,深入探讨 Django 的单元测试。通过本文的实战案例和详细讲解ÿ…...
pytorch 42 C#使用onnxruntime部署内置nms的yolov8模型
在进行目标检测部署时,通常需要自行编码实现对模型预测结果的解码及与预测结果的nms操作。所幸现在的各种部署框架对算子的支持更为灵活,可以在模型内实现预测结果的解码,但仍然需要自行编码实现对预测结果的nms操作。其实在onnx opset===11版本以后,其已支持将nms操作嵌入…...
【Lua】(一)VSCode 搭建 Lua 开发环境
前言 最近在找工作,基本所有的岗位都会问到 Lua(甚至拼 UI 的都要求会 Lua),咱能怎么办呢,咱也只能学啊…… 工欲善其事,必先利其器。第一步,先来把环境配置好吧! 当前适用版本&a…...
react-vite-antd环境下新建项目
vite 创建一个react项目 1. 安装vite并创建一个react项目1. 我使用的 yarn安装,基本配置项目名字, 框架react ,js2. cd vite-react进入项目目录安装node包并启动项目 2. 安装引入Ant Design引入依赖(我用的yarn,没有安装的也可以使…...
KeilMDk软仿真设置_STM32F03C8
1、KeilMDK软仿真的价值 (1)在没有硬件的情况下进行程序的编写调试。 (2)避免频繁的下载程序,延长单片机Flash寿命。 2、软仿真配置。 (1)打开Keil工程。 (2)点击“Options for Target ***”,如下图所示。 (3)点击“Debug”。 (4)进行如下配置。 U…...
mysql的隐式连接和显式连接的区别
隐式连接(Implicit Join)和显式连接(Explicit Join)是 SQL 查询中用于联结多个表的两种不同语法方式。它们的区别主要体现在语法的书写风格和可读性上。 隐式连接: 隐式连接使用逗号 , 将多个表名放在 FROM 子句中&am…...
vue-element-admin新增view后点击侧边栏加载慢问题
按照官网文档新增view 新增之后点击显示一直在加载中 解决方案:删除script中这段代码...
Nodejs后端服务接入Taotoken多模型API的完整配置指南
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Nodejs后端服务接入Taotoken多模型API的完整配置指南 对于Node.js后端开发者而言,将大模型能力集成到服务中已成为提升…...
如何在10分钟内搭建AI与Figma双向通信系统:TalkToFigma MCP完整指南
如何在10分钟内搭建AI与Figma双向通信系统:TalkToFigma MCP完整指南 【免费下载链接】cursor-talk-to-figma-mcp TalkToFigma: MCP integration between AI Agent (Cursor, Claude Code) and Figma, allowing Agentic AI to communicate with Figma for reading des…...
如何快速批量添加专业水印:3分钟掌握摄影作品保护终极指南
如何快速批量添加专业水印:3分钟掌握摄影作品保护终极指南 【免费下载链接】semi-utils 一个批量添加相机机型和拍摄参数的工具,后续「可能」添加其他功能。 项目地址: https://gitcode.com/gh_mirrors/se/semi-utils semi-utils是一款专为摄影师…...
告别重复操作:M9A如何用智能自动化重塑《重返未来:1999》游戏体验
告别重复操作:M9A如何用智能自动化重塑《重返未来:1999》游戏体验 【免费下载链接】M9A 重返未来:1999 小助手 | Assistant For Reverse: 1999 项目地址: https://gitcode.com/gh_mirrors/m9/M9A 在当今快节奏的生活中,游戏…...
JoyCon-Driver:让Switch手柄在Windows上重获新生的完整方案
JoyCon-Driver:让Switch手柄在Windows上重获新生的完整方案 【免费下载链接】JoyCon-Driver A vJoy feeder for the Nintendo Switch JoyCons and Pro Controller 项目地址: https://gitcode.com/gh_mirrors/jo/JoyCon-Driver 你是否曾经想过,让闲…...
5分钟解决Mac NTFS读写难题:免费开源工具完全指南
5分钟解决Mac NTFS读写难题:免费开源工具完全指南 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, and management for NT…...
ArcGIS Pro 10.8 加载天地图WMTS服务,解决偏移问题的完整配置流程
ArcGIS Pro 10.8 精准集成天地图WMTS服务的全流程解析与偏移修正方案 在专业地理信息处理领域,底图数据的精准配准直接影响空间分析的可靠性。作为国内权威地理信息平台,天地图提供的WMTS服务因其标准化接口和权威数据源,成为GIS工程中的首选…...
《我的世界》EcoEnchants插件汉化与编译全流程:从Github源码到可用的中文版插件
《我的世界》EcoEnchants插件深度汉化与编译实战:从源码到定制化中文体验 引言:为什么我们需要自己动手编译与汉化? 在《我的世界》服务器生态中,EcoEnchants作为最受欢迎的附魔扩展插件之一,为游戏带来了数百种全新…...
C语言实战:从零构建2048游戏,掌握核心算法与图形编程
1. 项目概述与核心思路 作为一个写了十几年代码的老程序员,我始终认为,学习一门编程语言最有效的方式,不是死记硬背语法,而是动手去实现一个完整的、有成就感的项目。今天,我们就来聊聊如何用C语言,从零开始…...
OpenClaw 把 Context 管理抽象成了可插拔的 Context Engine,为什么要做这层抽象?这个设计能支持哪些不同的策略?
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:AI大模型原理和应用面试题 文章目录 一、🍀回答重点 二、🍀扩展知识 2.1 ☘️内置的 legacy 引擎 2.2 ☘️可以实现的高级策略 2.3 ☘️插件注册机制 …...
