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…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
