【spring专题】spring如何解析配置类和扫描包路径
文章目录
- 目标
- 重要的组件
- 加载配置类
- 启动解析组件
- 定位配置类
- 解析配置类
- 扫描过程
- 总结
目标
这是我们使用注解方式启动spring容器的核心代码
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User user = (User) applicationContext.getBean("user");
user.printName();
其中配置类MyConfig的代码是
@ComponentScan(value = "com.mydemo")
public class MyConfig {
}
现在我们的目标是搞清楚spring是怎么解析这个配置类并且扫描该配置类包路径下的bean?
重要的组件
- AnnotatedBeanDefinitionReader : spring容器启动的时候就会创建这个读取器,主要是将类以BeanDefinition的方式保存到bean工厂(DefaultListableBeanFactory)
在创建这个读取器的时候,spring会默认添加一个ConfigurationClassPostProcessor的BeanDefinition,这个就是在解析配置类时的主要对象,在AnnotationConfigUtils类的registerAnnotationConfigProcessors中实现
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
- ClassPathBeanDefinitionScanner : 路径扫描器,在spring启动的时候就会创建,主要功能就是对类路径进行扫描,内含一些扫描规则,例如在创建时候就会内置一个Component注解的过滤器
protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));...
}
加载配置类
我们的配置类是由AnnotatedBeanDefinitionReader类的doRegisterBean方法,转成BeanDefinition存到bean工厂的beanDefinitionMap中,基于ASM获取一个类信息转成BeanDefinition。
转成的核心代码
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
得到配置类对象的AnnotatedGenericBeanDefinition后,虽然还没有加载类,但是已经获取到了类的注解信息。
虽然都是带有BeanDefinition,但是保存到bean工厂的BeanDefinition和这个是不一样的,这个AnnotatedGenericBeanDefinition主要是一些注解信息,并没有类似于BeanDefinition的属性,如是否懒加载,作用域,是否依赖等。
解析AnnotatedGenericBeanDefinition注解信息的主要代码,主要就是读取Lazy、Primary 、DependsOn、Description设置成属性值
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}
}if (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {abd.setDependsOn(dependsOn.getStringArray("value"));
}AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {abd.setDescription(description.getString("value"));
}
解析AnnotatedGenericBeanDefinition后转成BeanDefinitionHolder才是我们要保存到bean工厂的BeanDefinition
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
如果配置类不是代理模式,就直接保存BeanDefinition到bean工厂中了,
如果是代理模式,就创建一个新的RootBeanDefinition保存到bean工厂中,主要实现的代码在ScopedProxyUtils类createScopedProxy方法中
启动解析组件
spring在启动配置类扫描的任务时,是以启动一个BeanDefinitionRegistryPostProcessor的方式调用扫描类执行的,属于一种组件化启动任务类的方式
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {...postProcessor.postProcessBeanDefinitionRegistry(registry);...
}
这个组件的实现类是ConfigurationClassPostProcessor,所以所有的扫描代码都在该类的postProcessBeanDefinitionRegistry方法下
定位配置类
在bean工厂的beanDefinitionMap中遍历每个元素来定位符合配置类的bd,规则校验在ConfigurationClassUtils类checkConfigurationClassCandidate方法中:
- 主要是确定该bd是AnnotatedBeanDefinition类型,
- 如果beanDef不是AnnotatedBeanDefinition的实例,则进一步检查它是否是AbstractBeanDefinition的实例并且已经有了对应的Class对象。如果是的话,接着会检查这个Class是否实现了某些特定接口(如BeanFactoryPostProcessor, BeanPostProcessor, AopInfrastructureBean, 或者EventListenerFactory)。如果确实实现了这些接口中的一个或多个,函数将返回false,表示不需要继续解析。否则,它将通过AnnotationMetadata.introspect(beanClass)方法来获取该类的注解元数据。
- 如果以上两种情况都不满足,代码将尝试通过MetadataReader从类路径中读取指定类名(className)的元数据。这通常涉及到加载类文件并从中提取信息。如果在这个过程中发生IO异常(例如找不到类文件),则记录错误信息并返回false。
解析配置类
解析的操作是ConfigurationClassParser来完成的,所有解析的相关逻辑都在该类的processConfigurationClass方法中,主要负责解析和注册配置类中的各种注解:
处理@PropertySource @ComponentScan @Import @ImportResour @Bean注解,这里值分析 @ComponentScan注解,因为已经获取到了类的元信息,所以就可以获取@ComponentScan配置的路径,进而进行路径扫描,扫描是交由ComponentScanAnnotationParser组件执行的,由ComponentScanAnnotationParser组件发起最终在ClassPathBeanDefinitionScanner类型的doScan来实现
扫描过程
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
通过调用findCandidateComponents方法,根据提供的基础包名(basePackage)来查找该包及其子包下的所有符合组件扫描条件的类,并将它们作为候选组件返回。每个候选组件都是一个BeanDefinition对象,表示潜在的Spring bean:
- 构建搜索路径:
构建一个资源模式路径,用于指示ResourcePatternResolver在哪里查找资源。这个路径包括了类路径前缀、基础包名以及资源模式(例如/**/*.class),以便于匹配所有的类文件。
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;
- 获取资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
通过getResourcePatternResolver()获取资源解析器实例,并调用其getResources方法来获取与给定模式匹配的所有资源。这里的资源是指符合路径模式的类文件。
- 初步筛选
遍历每个资源,使用MetadataReaderFactory为每个资源创建一个MetadataReader实例,它能够读取类的元数据而无需加载该类到JVM中。
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
首先使用isCandidateComponent(metadataReader)方法初步判断资源是否可能是一个候选组件:
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
- 类必须是独立的(非内部类)。
- 同时,类必须是具体的(非接口或非抽象类),或者如果是抽象类的话,它必须包含至少一个用 @Lookup 注解标记的方法。
- 确定是否创建为BeanDefinition
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
对于每个候选的BeanDefinition,使用scopeMetadataResolver解析其作用域(scope)信息,同时为Bean生成或获取一个唯一的beanName
if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
如果候选Bean是一个AbstractBeanDefinition类型的实例,则调用postProcessBeanDefinition方法进行额外的后处理,比如应用默认值和自动装配规则
if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
如果候选Bean是AnnotatedBeanDefinition类型,那么将处理常见的注解,如@Lazy, @Primary, @DependsOn, @Role, 和 @Description等
if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);
}
检查当前候选Bean是否可以被注册到容器中,如果可以,继续执行以下操作:
创建一个BeanDefinitionHolder对象,该对象持有Bean定义、Bean名称以及其他元数据,
如果需要使用applyScopedProxyMode根据作用域代理模式来创建作用域代理,
将处理后的BeanDefinitionHolder添加到beanDefinitions列表,并注册到registry中。
在checkCandidate中还有一个方法
protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) {return (!(existingDef instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean(newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) || // scanned same file twicenewDef.equals(existingDef)); // scanned equivalent class twice
}
检查新的Bean定义是否与已存在的Bean定义兼容,避免重复扫描同一个文件或者类而引起的冲突。
总结
- 配置类加载:使用AnnotatedBeanDefinitionReader将配置类转换为BeanDefinition,并通过ASM库获取类信息。
- 启动解析组件:通过实现BeanDefinitionRegistryPostProcessor接口的ConfigurationClassPostProcessor组件来启动配置类的解析任务。
- 定位与解析配置类:遍历bean工厂中的所有BeanDefinition以定位配置类,并使用ConfigurationClassParser处理配置类上的各种注解,如@ComponentScan。
- 组件扫描:ClassPathBeanDefinitionScanner根据指定的基础包名查找符合组件扫描条件的类,进行初步筛选后创建BeanDefinition对象,最终注册到Spring容器中。
相关文章:
【spring专题】spring如何解析配置类和扫描包路径
文章目录 目标重要的组件加载配置类启动解析组件定位配置类解析配置类 扫描过程总结 目标 这是我们使用注解方式启动spring容器的核心代码 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(MyConfig.class); User user (Us…...
MyBatis框架的入门
目录 MyBatis第一章:框架的概述1. MyBatis框架的概述 第二章:MyBatis的入门程序1. 创建数据库和表结构2. MyBatis的入门步骤 MyBatis 第一章:框架的概述 1. MyBatis框架的概述 MyBatis是一个优秀的基于Java的持久层框架,内部对…...
代码随想录D22-23 回溯算法01-02 Python
理论回顾 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。 回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝…...
【网络云计算】2024第50周-每日【2024/12/13】小测-理论-写10个Bash Shell脚本-解析
文章目录 1. 计算1到100的和2. 列出当前目录下所有文件和文件夹3. 检查文件是否存在4. 备份文件到指定目录(简单示例)5. 打印系统当前日期和时间6. 统计文件中的行数7. 批量重命名文件(将.txt后缀改为.bak)8. 查找进程并杀死&…...
MATLAB转换C语言--问题(一)FFT 和 IFFT 的缩放因子
1. MATLAB 中的 FFT 和 IFFT 在 MATLAB 中,fft 和 ifft 函数具有以下缩放行为: fft:执行快速傅里叶变换(FFT),不进行缩放。ifft:执行逆快速傅里叶变换(IFFT),…...
轻松上手:使用 Vercel 部署 HTML 页面教程
😀 在学习前端的过程中,部署项目往往是一个令人头疼的问题。然而,Vercel 为我们提供了一个便捷且免费的解决方案。 Vercel 是一个强大的云平台,专门用于前端项目的部署和托管。它不仅支持多种前端框架和静态网站生成器࿰…...
如何运用 HTM?
一、HTM 概述 HTM(Hierarchical Temporal Memory,分层时序记忆)是一种基于神经科学原理构建的计算模型,旨在模拟大脑的学习和记忆机制,以处理复杂的时间序列数据和模式识别任务。它具有独特的架构和算法,能…...
12.16【net】【study】
路由表是路由器或者其他互联网网络设备上存储的一张表,它记录了到达特定网络目的地的路径。路由表中的每一行(即一个路由条目)包含了目的地网络地址、子网掩码、下一跳地址、出接口等信息。 Destinations(目的地)和 R…...
2023和2024历年美赛数学建模赛题,算法模型分析!
文末获取历年优秀论文解析,可交流解答 2023年题目分析 MCM(Mathematical Contest in Modeling) 问题 A:遭受旱灾的植物群落 概述:要求建立预测模型,模拟植物群落在干旱和降水充裕条件下随时间的变化。类…...
Node.js内置模块
1.内置模块 Node.js的中文网参考手册:https://nodejs.cn//api 帮助文档 API文档:查看对应的模块,左边是模块,右边是模块的成员 源码:https://github.com/nodejs/node/tree/main/lib 查看 例如: http.js 创建web服务器的模块 -->进入源码中,搜索…...
测评|携程集团25年社招在线测评北森题库、真题分析、考试攻略
携程集团社招入职测评北森题库主要考察以下几个方面: 1. **言语理解**:这部分主要测试应聘者运用语言文字进行思考和交流、迅速准确地理解和把握文段要旨的能力。 2. **资料分析**:包括文字题和图表题,考察应聘者快速找出关键信息…...
快速启动Go-Admin(Gin + Vue3 + Element UI)脚手架管理系统
Go-Admin 是一个基于 Gin Vue Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架。它包含了多租户支持、基础用户管理功能、JWT 鉴权、代码生成器、RBAC 资源控制、表单构建、定时任务等功能。该项目的主要编程语言是 Go 和 JavaScript。 ps&a…...
数据分流:优化数据处理流程的关键策略
引言 在大数据时代,企业面临着数据量的激增和数据类型的多样化。为了有效地管理和分析这些数据,数据分流成为了一个重要的策略。数据分流指的是将数据按照特定的规则和流程分配到不同的处理路径,以优化数据处理效率和准确性。本文将探讨数据…...
RabbitMQ如何构建集群?
大家好,我是锋哥。今天分享关于【RabbitMQ如何构建集群?】面试题。希望对大家有帮助; RabbitMQ如何构建集群? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在RabbitMQ中,集群(Cluster&#x…...
RNN LSTM Seq2Seq Attention
非端到端: data -》 cleaning -》 feature Engining (70%-80%工作 设计特征)-》 分类器 -》预测 端到端 End-to-End: data -》 cleaning -》Deep learning(表示学习,从数据中学习特征) -》…...
硬件设计-ADC和低本底噪声为何至关重要
简介 在工程领域,精度是核心要素。无论是对先进电子设备执行质量和性能检测,还是对复杂系统进行调试,测量精度的高低都直接关系到项目的成功与否。这时,示波器中的垂直精度概念就显得尤为重要,它衡量的是电压与实际被…...
个性化域名配置
1 申请免费SSL证书 访问 https://certbot.eff.org ,可申请 通配符证书,每次申请可以使用3个月,到期可以免费续期。 2 配置nginx server index.conf 配置如下: server {listen 80;server_name biwow.com www.biwow.com;return …...
uniapp中打包应用后,组件在微信小程序和其他平台实现不同的样式
今天,我们来介绍一下,uniapp中如何实现打包应用后,组件在微信小程序和其他平台不同的样式,在这里,我们使用背景颜色进行演示,使用 UniApp 提供的 uni.getSystemInfoSync() 方法来获取系统信息,包…...
MRI脑肿瘤检测数据集,使用500张原始图片标注,支持yolo,coco,voc格式
MRI脑肿瘤检测数据集,使用500张原始图片标注,支持yolo,coco,voc格式 数据集下载: https://download.csdn.net/download/pbymw8iwm/90125474 https://download.csdn.net/download/pbymw8iwm/90125473 https://downl…...
JumpServer开源堡垒机搭建及使用
目录 一,产品介绍 二,功能介绍 三,系统架构 3.1 应用架构 3.2 组件说明 3.3 逻辑架构 3.3 逻辑架构 四,linux单机部署及方式选择 4.1 操作系统要求(JumpServer-v3系列版本) 4.1.1 数据库 4.1.3创建数据库参考 4.2 在线安装 4.2.1 环境访问 4.3 基于docker容…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
