Spring源码分析之事件机制——观察者模式(二)
目录
获取监听器的入口方法
实际检索监听器的核心方法
监听器类型检查方法
监听器的注册过程
监听器的存储结构
过程总结
Spring源码分析之事件机制——观察者模式(一)-CSDN博客
Spring源码分析之事件机制——观察者模式(二)-CSDN博客
Spring源码分析之事件机制——观察者模式(三)-CSDN博客
这两篇文章是这个篇章的前篇和后篇,感兴趣的读者可以阅读一下,从spring源码分析观察者模式
获取监听器的入口方法


public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster {// 存储所有监听器的集合private final DefaultListenerRetrieverdefaultRetriever = new DefaultListenerRetriever();// 缓存事件类型与监听器的映射关系private final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {// 获取事件源和源类型Object source = event.getSource();Class<?> sourceType = source != null ? source.getClass() : null;// 创建缓存keyListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);// 准备新的缓存检索器CachedListenerRetriever newRetriever = null;// 尝试从缓存中获取已存在的检索器CachedListenerRetriever existingRetriever = (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 CachedListenerRetriever();// 使用CAS操作将新检索器放入缓存existingRetriever = (CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);if (existingRetriever != null) {newRetriever = null;}}// 如果存在缓存的检索器,尝试获取缓存的监听器if (existingRetriever != null) {Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();if (result != null) {return result;}}// 如果缓存未命中,检索匹配的监听器return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}
这个方法实现了监听器检索的缓存机制,通过缓存来提高性能,同时考虑了类加载器安全性。
实际检索监听器的核心方法
private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {// 存储所有匹配的监听器List<ApplicationListener<?>> allListeners = new ArrayList();// 如果有缓存检索器,创建过滤后的监听器集合Set<ApplicationListener<?>> filteredListeners = retriever != null ? new LinkedHashSet() : null;Set<String> filteredListenerBeans = retriever != null ? new LinkedHashSet() : null;// 同步获取已注册的监听器和监听器Bean名称Set<ApplicationListener<?>> listeners;Set<String> listenerBeans;synchronized(this.defaultRetriever) {listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);}// 处理已实例化的监听器for(ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}// 处理监听器Beanif (!listenerBeans.isEmpty()) {ConfigurableBeanFactory beanFactory = getBeanFactory();for(String listenerBeanName : listenerBeans) {try {// 检查Bean是否支持该事件if (supportsEvent(beanFactory, listenerBeanName, eventType)) {ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);// 避免重复添加if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {// 根据Bean的作用域决定是否缓存if (retriever != null) {if (beanFactory.isSingleton(listenerBeanName)) {filteredListeners.add(listener);} else {filteredListenerBeans.add(listenerBeanName);}}allListeners.add(listener);}} else {// 移除不支持的监听器Object listener = beanFactory.getSingleton(listenerBeanName);if (retriever != null) {filteredListeners.remove(listener);}allListeners.remove(listener);}} catch (NoSuchBeanDefinitionException ex) {// 忽略不存在的Bean}}}// 按照@Order注解排序AnnotationAwareOrderComparator.sort(allListeners);// 更新缓存if (retriever != null) {if (filteredListenerBeans.isEmpty()) {retriever.applicationListeners = new LinkedHashSet(allListeners);retriever.applicationListenerBeans = filteredListenerBeans;} else {retriever.applicationListeners = filteredListeners;retriever.applicationListenerBeans = filteredListenerBeans;}}return allListeners;
}
这个方法是实际检索监听器的核心实现,它处理了已实例化的监听器和尚未实例化的监听器Bean,同时考虑了Bean的作用域和缓存策略。
监听器类型检查方法
private boolean supportsEvent(ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {// 获取监听器的类型Class<?> listenerType = beanFactory.getType(listenerBeanName);// 如果是特殊的监听器类型,直接返回trueif (listenerType != null && !GenericApplicationListener.class.isAssignableFrom(listenerType) && !SmartApplicationListener.class.isAssignableFrom(listenerType)) {// 检查是否支持事件类型if (!supportsEvent(listenerType, eventType)) {return false;}try {// 获取Bean定义并检查泛型类型BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric(new int[0]);return genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType);} catch (NoSuchBeanDefinitionException ex) {return true;}}return true;
}
这个方法负责检查监听器是否支持特定的事件类型,它考虑了泛型类型和特殊的监听器接口。整个实现展示了Spring如何高效地管理和匹配事件监听器,通过缓存机制提高性能,同时保证类型安全和正确的监听器顺序。这种实现既保证了功能的完整性,又确保了运行时的效率。
监听器的注册过程

public abstract class AbstractApplicationContext {// 在容器刷新过程中注册监听器protected void registerListeners() {// 首先注册静态指定的监听器for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 获取配置的监听器Bean名称String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {// 将监听器Bean名称添加到多播器getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 发布早期事件Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}
}
这段代码展示了Spring如何在容器启动时注册监听器。对于像UserCacheListener这样的组件,它们会被Spring容器扫描并注册到ApplicationEventMulticaster中。
监听器的存储结构

private class CachedListenerRetriever {@Nullablepublic volatile Set<ApplicationListener<?>> applicationListeners;@Nullablepublic volatile Set<String> applicationListenerBeans;private CachedListenerRetriever() {}@Nullablepublic Collection<ApplicationListener<?>> getApplicationListeners() {Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;Set<String> applicationListenerBeans = this.applicationListenerBeans;if (applicationListeners != null && applicationListenerBeans != null) {List<ApplicationListener<?>> allListeners = new ArrayList(applicationListeners.size() + applicationListenerBeans.size());allListeners.addAll(applicationListeners);if (!applicationListenerBeans.isEmpty()) {BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory();for(String listenerBeanName : applicationListenerBeans) {try {allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));} catch (NoSuchBeanDefinitionException var8) {}}}if (!applicationListenerBeans.isEmpty()) {AnnotationAwareOrderComparator.sort(allListeners);}return allListeners;} else {return null;}}}
CachedListenerRetriever 其实是AbstractApplicationEventMulticaster 的静态内部类
过程总结
Spring扫描到@Component注解,创建BeanDefinition
Spring容器启动时实例化Bean
在AbstractApplicationContext.registerListeners()中注册
相关文章:
Spring源码分析之事件机制——观察者模式(二)
目录 获取监听器的入口方法 实际检索监听器的核心方法 监听器类型检查方法 监听器的注册过程 监听器的存储结构 过程总结 Spring源码分析之事件机制——观察者模式(一)-CSDN博客 Spring源码分析之事件机制——观察者模式(二ÿ…...
热备份路由HSRP及配置案例
✍作者:柒烨带你飞 💪格言:生活的情况越艰难,我越感到自己更坚强;我这个人走得很慢,但我从不后退。 📜系列专栏:网路安全入门系列 目录 一,HSRP的相关概念二,…...
仿生的群体智能算法总结之三(十种)
群体智能算法是一类通过模拟自然界中的群体行为来解决复杂优化问题的方法。以下是30种常见的群体智能算法,本文汇总第21-30种。接上文 : 编号 算法名称(英文) 算法名称(中文) 年份 作者 1 Ant Colony Optimization (ACO) 蚁群优化算法 1991 Marco Dorigo 2 Particle Swar…...
CentOS 7系统 OpenSSH和OpenSSL版本升级指南
文章目录 CentOS 7系统 OpenSSH和OpenSSL版本升级指南环境说明当前系统版本当前组件版本 现存安全漏洞升级目标版本升级准备工作OpenSSL升级步骤1. 下载和解压2. 编译安装3. 配置环境 OpenSSH升级步骤1. 下载和解压2. 编译安装3. 创建systemd服务配置4. 更新SSH配置文件5. 设置…...
【专题】2024年出口跨境电商促销趋势白皮书报告汇总PDF洞察(附原数据表)
原文链接:https://tecdat.cn/?p38722 在当今全球化加速演进、数字经济蓬勃发展的大背景下,跨境电商行业正以前所未有的态势重塑国际贸易格局,成为各方瞩目的焦点领域。 根据亚马逊发布的《2024年出口跨境电商促销趋势白皮书》,…...
【Ubuntu】不能连上网络
1. ping路由器的IP地址 ping 192.168.1.1 如果ping不通的话,可能是网络故障导致的。需要重启配置ip地址。配置文件 sudo vi /etc/network/interface 2. ping 8.8.8.8 如果ping不通的话,可能是路由器不能链接往外网; 或者路由器显示了当…...
CSS3 框大小
CSS3 框大小 CSS3 是网页设计和开发中不可或缺的一部分,它为开发者提供了更多样化、更灵活的样式和布局选择。在 CSS3 中,框大小(Box Sizing)是一个重要的概念,它决定了元素内容的宽度和高度以及元素整体的大小。本文将详细介绍 CSS3 框大小的概念、用法以及最佳实践。 …...
联发科MTK6771/MT6771安卓核心板规格参数介绍
MT6771,也被称为Helio P60,是联发科技(MediaTek)推出的一款中央处理器(CPU)芯片,可运行 android9.0 操作系统的 4G AI 安卓智能模块。MT6771芯片采用了12纳米工艺制造,拥有八个ARM Cortex-A73和Cortex-A53核心,主频分别…...
python中的时间模块--datetime模块、time模块
python中的时间模块 一.datetime模块二.time模块 一.datetime模块 引入时间模块 from datetime import datetime获取当前时间 print(datetime.today()) # 前的日期和时间 print(datetime.now()) # 当前的日期和时间 print(datetime.now().year) # 当前的年份 print(datetime…...
CV 处理全流程:从数据采集到模型部署的整个过程,体现全面性
CV 处理全流程:从数据采集到模型部署的整个过程,体现全面性 Numpy广播 OpenCV - Python归一化提取ROI(感兴趣区域)分离和合并通道 Pytorch 基础算子自动梯度计算 CV 全流程图像数据采集1. 确认目标2. 分析过程(使用目标-手段分析法࿰…...
OWASP ZAP之API 请求基础知识
ZAP API 提供对 ZAP 大部分核心功能的访问,例如主动扫描器和蜘蛛。ZAP API 在守护进程模式和桌面模式下默认启用。如果您使用 ZAP 桌面,则可以通过访问以下屏幕来配置 API: Tools -> Options -> API。 ZAP 需要 API 密钥才能通过 REST API 执行特定操作。必须在所有 …...
南京观海微电子----GH7009国宇测试盒使用
1. SPI接线 针对7009: 2. 国宇上位机代码准备 在主函数首尾两端加入IO2时序控制的代码、以及国语SPI有效位控制的代码(请注意7009和其他700x使用的有效位控制不一致,需要用哪一款加入哪一行即可): 三、国宇SPI读的使…...
mysql及其兼容语法数据库对于注释的特殊要求
我司大部分数据库使用MS-SQL,其中使用大量–开头的行注释,由于业务需要,切换到了Starrocks数据库(兼容mysql语法)后发现使用with开头子查询的时候,大量报错,单独执行内部的子查询又正常…...
数据去重与重复数据的高效处理策略
在实际业务中,数据去重是一个非常常见的需求,特别是在日志数据、用户操作记录或交易记录等领域。去重不仅仅是删除重复数据,更重要的是按照业务规则保留最有价值的数据记录。 本文将探讨如何在 SQL 中高效地处理重复数据,通过 DI…...
Spring Boot自动装配代码详解
概述 Spring Boot自动装配是其核心特性之一,它能够根据项目中添加的依赖自动配置Spring应用程序。通过自动装配,开发人员可以减少大量的配置工作,快速搭建起一个可用的Spring应用。 关键组件和注解 SpringBootApplication注解 这是Spring Bo…...
渗透测试-非寻常漏洞案例
声明 本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法. 此文章不允许未经授权转发至除先知社区以外的其它平台!࿰…...
122. 买卖股票的最佳时机 II
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/?envTypestudy-plan-v2&envIdtop-interview-150问题分析: 和买卖股票的最佳时机I这题相比,区别就是可以买多只股票虽然同时只能持有一支,但是我们还是可以…...
Python爬虫入门指南:从零开始抓取数据
Python爬虫入门指南:从零开始抓取数据 引言 在大数据时代,数据是新的石油。而爬虫作为获取数据的重要手段,受到了越来越多的关注。Python作为一门强大的编程语言,其简洁易用的特性使得它成为爬虫开发的首选语言。本篇文章将带你…...
Android使用JAVA调用JNI原生C++方法
1.native-lib.cpp为要生成so库的源码文件 2.JNI函数声明说明 NewStringUTF函数会返回jstring JNI函数声明规则 3.JAVA中声明及调用JNI函数 声明: 调用 4.源码地址: gitgithub.com:tonyimax/UpdateTimeByThread.git...
ros常用命令记录
文章目录 1.基本2.rosbag2.1录制rosbag包2.2播放录制的ROS包 3.生命周期4.ROS启动,roslaunch5.ROS消息发布6.ROS消息后台打印监控 1.基本 ros2 topic list #查看话题列表2.rosbag 2.1录制rosbag包 ros2 bag record <topic_name> #记录单个主题消息 ros2 ba…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
Linux入门课的思维导图
耗时两周,终于把慕课网上的Linux的基础入门课实操、总结完了! 第一次以Blog的形式做学习记录,过程很有意思,但也很耗时。 课程时长5h,涉及到很多专有名词,要去逐个查找,以前接触过的概念因为时…...
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
报错 找到package.json文件 找到这个修改成 "lint": "eslint --fix --ext .js,.vue src" 为elsint有配置结尾换行符,最后运行:npm run lint --fix...
