当前位置: 首页 > news >正文

【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方法中:

  1. 主要是确定该bd是AnnotatedBeanDefinition类型,
  2. 如果beanDef不是AnnotatedBeanDefinition的实例,则进一步检查它是否是AbstractBeanDefinition的实例并且已经有了对应的Class对象。如果是的话,接着会检查这个Class是否实现了某些特定接口(如BeanFactoryPostProcessor, BeanPostProcessor, AopInfrastructureBean, 或者EventListenerFactory)。如果确实实现了这些接口中的一个或多个,函数将返回false,表示不需要继续解析。否则,它将通过AnnotationMetadata.introspect(beanClass)方法来获取该类的注解元数据。
  3. 如果以上两种情况都不满足,代码将尝试通过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()))));
  1. 类必须是独立的(非内部类)。
  2. 同时,类必须是具体的(非接口或非抽象类),或者如果是抽象类的话,它必须包含至少一个用 @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定义兼容,避免重复扫描同一个文件或者类而引起的冲突。

总结

  1. 配置类加载:使用AnnotatedBeanDefinitionReader将配置类转换为BeanDefinition,并通过ASM库获取类信息。
  2. 启动解析组件:通过实现BeanDefinitionRegistryPostProcessor接口的ConfigurationClassPostProcessor组件来启动配置类的解析任务。
  3. 定位与解析配置类:遍历bean工厂中的所有BeanDefinition以定位配置类,并使用ConfigurationClassParser处理配置类上的各种注解,如@ComponentScan。
  4. 组件扫描:ClassPathBeanDefinitionScanner根据指定的基础包名查找符合组件扫描条件的类,进行初步筛选后创建BeanDefinition对象,最终注册到Spring容器中。

相关文章:

【spring专题】spring如何解析配置类和扫描包路径

文章目录 目标重要的组件加载配置类启动解析组件定位配置类解析配置类 扫描过程总结 目标 这是我们使用注解方式启动spring容器的核心代码 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(MyConfig.class); User user (Us…...

MyBatis框架的入门

目录 MyBatis第一章&#xff1a;框架的概述1. MyBatis框架的概述 第二章&#xff1a;MyBatis的入门程序1. 创建数据库和表结构2. MyBatis的入门步骤 MyBatis 第一章&#xff1a;框架的概述 1. MyBatis框架的概述 MyBatis是一个优秀的基于Java的持久层框架&#xff0c;内部对…...

代码随想录D22-23 回溯算法01-02 Python

理论回顾 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff0c;如果想让回溯法高效一些&#xff0c;可以加一些剪枝…...

【网络云计算】2024第50周-每日【2024/12/13】小测-理论-写10个Bash Shell脚本-解析

文章目录 1. 计算1到100的和2. 列出当前目录下所有文件和文件夹3. 检查文件是否存在4. 备份文件到指定目录&#xff08;简单示例&#xff09;5. 打印系统当前日期和时间6. 统计文件中的行数7. 批量重命名文件&#xff08;将.txt后缀改为.bak&#xff09;8. 查找进程并杀死&…...

MATLAB转换C语言--问题(一)FFT 和 IFFT 的缩放因子

1. MATLAB 中的 FFT 和 IFFT 在 MATLAB 中&#xff0c;fft 和 ifft 函数具有以下缩放行为&#xff1a; fft&#xff1a;执行快速傅里叶变换&#xff08;FFT&#xff09;&#xff0c;不进行缩放。ifft&#xff1a;执行逆快速傅里叶变换&#xff08;IFFT&#xff09;&#xff0c;…...

轻松上手:使用 Vercel 部署 HTML 页面教程

&#x1f600; 在学习前端的过程中&#xff0c;部署项目往往是一个令人头疼的问题。然而&#xff0c;Vercel 为我们提供了一个便捷且免费的解决方案。 Vercel 是一个强大的云平台&#xff0c;专门用于前端项目的部署和托管。它不仅支持多种前端框架和静态网站生成器&#xff0…...

如何运用 HTM?

一、HTM 概述 HTM&#xff08;Hierarchical Temporal Memory&#xff0c;分层时序记忆&#xff09;是一种基于神经科学原理构建的计算模型&#xff0c;旨在模拟大脑的学习和记忆机制&#xff0c;以处理复杂的时间序列数据和模式识别任务。它具有独特的架构和算法&#xff0c;能…...

12.16【net】【study】

路由表是路由器或者其他互联网网络设备上存储的一张表&#xff0c;它记录了到达特定网络目的地的路径。路由表中的每一行&#xff08;即一个路由条目&#xff09;包含了目的地网络地址、子网掩码、下一跳地址、出接口等信息。 Destinations&#xff08;目的地&#xff09;和 R…...

2023和2024历年美赛数学建模赛题,算法模型分析!

文末获取历年优秀论文解析&#xff0c;可交流解答 2023年题目分析 MCM&#xff08;Mathematical Contest in Modeling&#xff09; 问题 A&#xff1a;遭受旱灾的植物群落 概述&#xff1a;要求建立预测模型&#xff0c;模拟植物群落在干旱和降水充裕条件下随时间的变化。类…...

Node.js内置模块

1.内置模块 Node.js的中文网参考手册:https://nodejs.cn//api 帮助文档 API文档:查看对应的模块,左边是模块,右边是模块的成员 源码:https://github.com/nodejs/node/tree/main/lib 查看 例如: http.js 创建web服务器的模块 -->进入源码中,搜索…...

测评|携程集团25年社招在线测评北森题库、真题分析、考试攻略

携程集团社招入职测评北森题库主要考察以下几个方面&#xff1a; 1. **言语理解**&#xff1a;这部分主要测试应聘者运用语言文字进行思考和交流、迅速准确地理解和把握文段要旨的能力。 2. **资料分析**&#xff1a;包括文字题和图表题&#xff0c;考察应聘者快速找出关键信息…...

快速启动Go-Admin(Gin + Vue3 + Element UI)脚手架管理系统

Go-Admin 是一个基于 Gin Vue Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架。它包含了多租户支持、基础用户管理功能、JWT 鉴权、代码生成器、RBAC 资源控制、表单构建、定时任务等功能。该项目的主要编程语言是 Go 和 JavaScript。 ps&a…...

数据分流:优化数据处理流程的关键策略

引言 在大数据时代&#xff0c;企业面临着数据量的激增和数据类型的多样化。为了有效地管理和分析这些数据&#xff0c;数据分流成为了一个重要的策略。数据分流指的是将数据按照特定的规则和流程分配到不同的处理路径&#xff0c;以优化数据处理效率和准确性。本文将探讨数据…...

RabbitMQ如何构建集群?

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ如何构建集群&#xff1f;】面试题。希望对大家有帮助&#xff1b; RabbitMQ如何构建集群&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在RabbitMQ中&#xff0c;集群&#xff08;Cluster&#x…...

RNN LSTM Seq2Seq Attention

非端到端&#xff1a; data -》 cleaning -》 feature Engining &#xff08;70%-80%工作 设计特征&#xff09;-》 分类器 -》预测 端到端 End-to-End&#xff1a; data -》 cleaning -》Deep learning&#xff08;表示学习&#xff0c;从数据中学习特征&#xff09; -》…...

硬件设计-ADC和低本底噪声为何至关重要

简介 在工程领域&#xff0c;精度是核心要素。无论是对先进电子设备执行质量和性能检测&#xff0c;还是对复杂系统进行调试&#xff0c;测量精度的高低都直接关系到项目的成功与否。这时&#xff0c;示波器中的垂直精度概念就显得尤为重要&#xff0c;它衡量的是电压与实际被…...

个性化域名配置

1 申请免费SSL证书 访问 https://certbot.eff.org &#xff0c;可申请 通配符证书&#xff0c;每次申请可以使用3个月&#xff0c;到期可以免费续期。 2 配置nginx server index.conf 配置如下&#xff1a; server {listen 80;server_name biwow.com www.biwow.com;return …...

uniapp中打包应用后,组件在微信小程序和其他平台实现不同的样式

今天&#xff0c;我们来介绍一下&#xff0c;uniapp中如何实现打包应用后&#xff0c;组件在微信小程序和其他平台不同的样式&#xff0c;在这里&#xff0c;我们使用背景颜色进行演示&#xff0c;使用 UniApp 提供的 uni.getSystemInfoSync() 方法来获取系统信息&#xff0c;包…...

MRI脑肿瘤检测数据集,使用500张原始图片标注,支持yolo,coco,voc格式

MRI脑肿瘤检测数据集&#xff0c;使用500张原始图片标注&#xff0c;支持yolo&#xff0c;coco&#xff0c;voc格式 数据集下载&#xff1a; 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容…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程

基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...

接口 RESTful 中的超媒体:REST 架构的灵魂驱动

在 RESTful 架构中&#xff0c;** 超媒体&#xff08;Hypermedia&#xff09;** 是一个核心概念&#xff0c;它体现了 REST 的 “表述性状态转移&#xff08;Representational State Transfer&#xff09;” 的本质&#xff0c;也是区分 “真 RESTful API” 与 “伪 RESTful AP…...