【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容器中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/64375.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL数据库备份,恢复

备份策略不同&#xff0c;恢复方式也不同。 在进行数据备份的时候&#xff0c;能使用冷备份就使用冷备份&#xff0c;安全可靠&#xff0c; 但是冷备份自己的缺点是【每一个数据库都对应着一个前端的业务】此时前端业务停止可能带来经济损失。 一.备份类型 根据服务是否在线…

算法(三)——贪心算法

文章目录 定义基本原理基本思路优缺点优点缺点 经典案例及解析找零问题问题描述贪心思路算法解析java代码示例 活动选择问题问题描述贪心思路算法解析java代码示例 车辆路径问题问题描述贪心思路算法分析java代码示例 定义 贪心算法是指在求解问题时&#xff0c;总是做出在当前…

代码随想录算法训练营第七天-哈希-454. 四数相加II

力扣原题链接&#xff1a;454. 四数相加 II使用map这个数据结构来保存前两个集合元素和的结果&#xff0c;value的值代表和这个值的出现的次数使用这个方法&#xff0c;可以让算法复杂度从 n 4 n^4 n4下降到 n 2 n^2 n2&#xff0c;效率会大大提高 #include <iostream> …

OpenCV 学习记录:首篇

最近在学习机器视觉&#xff0c;希望能通过记录博客的形式来鞭策自己坚持学完&#xff0c;同时也把重要的知识点记录下来供参考学习。 1. OpenCV 介绍与模块组成 什么是 OpenCV&#xff1f; OpenCV (Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习软…

【Rust自学】3.1. 变量与可变性

3.1.0. 写在正文之前 欢迎来到Rust自学的第三章&#xff0c;一共有6个小节&#xff0c;分别是: 变量与可变性&#xff08;本文&#xff09;数据类型&#xff1a;标量类型数据类型&#xff1a;复合类型函数和注释控制流&#xff1a;if else控制流&#xff1a;循环 通过第二章…

基于vue框架的的校园二手市场交易平台8k655(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,大学,商品分类,商品信息,在线咨询 开题报告内容 基于Vue框架的校园二手市场交易平台开题报告 一、课题意义 &#xff08;一&#xff09;理论意义 本课题旨在研究基于Vue框架的校园二手市场交易平台的设计与实现。当前&#xff…

3D计算机视觉概述

3D计算机视觉 3D计算机视觉概述 像机标定 文章目录 3D计算机视觉前言一、人类视觉二、计算机视觉2.1 计算机视觉的研究目的2.2 计算机视觉的研究任务2.3 计算机视觉的研究方法2.4 视觉计算理论2.5 马尔框架中计算机视觉表达的四个层次2.5.1 图像&#xff08;像素表达&#xff…

操作系统(13)虚拟存储器

前言 操作系统中的虚拟存储器是一项关键技术&#xff0c;它为用户提供了一个远大于实际物理内存容量的逻辑内存空间。 一、定义与原理 虚拟存储器是具有请求调入功能和置换功能&#xff0c;能从逻辑上对内存容量加以扩充的存储器系统。其逻辑容量由内存容量与外存容量之和决定&…

【实用技能】如何运用Visual Paradigm快速创建团队的项目模板

从 Visual Paradigm 17.2 &#xff08;&#xff09;版开始&#xff0c;您可以创建自己的项目模板并与团队共享。这样团队成员就可以轻松创建符合团队标准的新项目。本文将指导您完成为团队创建项目模板的过程。 Visual Paradigm v17.2试用版下载 先决条件 您的团队必须使用 …

不良人系列-复兴数据结构(栈和队列)

个人主页&#xff1a;爱编程的小新☆ 不良人经典语录&#xff1a;“相呴相济 玉汝于成 勿念 心安” 目录 一. 栈(stack) 1. 栈的概念 2. 栈的常见方法 3.栈的模拟实现 ​编辑 二. 队列 1. 队列的概念 2. 队列的使用 2.1 队列的常见方法 2.2 队列的模拟实现 2.3 队列…

【Linux网络】网络基础:IP协议

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ IP协议 IP协议基本概念协议头格式分片与组装网段划分子网掩码特殊的IP地址 IP地址的数量限制…

数据结构——常见数据结构和应用

数据结构是计算机科学中的一个基本概念&#xff0c;它涉及数据的组织、管理和存储方式。以下是对数据结构的详细解释&#xff1a; 一、定义与组成 数据&#xff1a;描述事物的符号记录&#xff0c;是计算机程序的输入和输出。它可以以多种形式存在&#xff0c;如数字、文字、…

Linux正则化与三剑客速成(一)

目录 1.正则化 1.1正则表达式&#xff08;RE&#xff09; 1.2 正则表达式的注意事项 1.3正则表达式的分类 1.4 基本正则表达式 ^:表示匹配文本中以某个字符串开头的行。 $:表示匹配以某个字符串结尾的文件内的行 ^$:表示空行&#xff0c;但是在Linux中的实际的操作中一…

HarmonyOS 非线性容器LightWeightMap 常用的几个方法

LightWeightMap可用于存储具有关联关系的key-value键值对集合&#xff0c;存储元素中key值唯一&#xff0c;每个key对应一个value。 LightWeightMap依据泛型定义&#xff0c;采用轻量级结构&#xff0c;初始默认容量大小为8&#xff0c;每次扩容大小为原始容量的两倍。 集合中k…

Docker的容器

目录 1. 什么是容器&#xff1f;2. 容器的生命周期2.1 容器处理OOM事件2.2 容器异常退出2.3 容器暂停 3. 容器命令详解3.1 容器命令清单3.2 docker create命令3.3 docker run命令3.4 docker ps命令3.5 docker logs命令3.6 docker attach命令3.7 docker exec命令3.8 docker stat…

《红队蓝队在网络安全对抗演练中的运作模式》

在网络安全领域&#xff0c;红队与蓝队的对抗性演练是一个复杂且系统的过程&#xff0c;主要包括以下阶段&#xff1a; 一、演练规划阶段 确定目标和范围 组织首先要明确演练的目的&#xff0c;例如测试新部署的网络安全防御系统的有效性、评估员工对网络安全威胁的应对能力或者…

LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 高级GLSL 内建变量 顶点着色器 gl_PointSoze : float 输出变量&#xff0c;用于控制渲染 GL_POINTS 型图元时&#xff0c;点的大小。可用于粒子系统。将其设置为 gl_Position.z 时&#xff0c;可以使点…

Excel/VBA 正则表达式归纳汇总

1.with结构。以下语句用来提取A列中的“成品”两个字前面的部分的中文&#xff0c;不含成品两个字&#xff0c;结果存放在第2列。使用了On Error Resume Next&#xff0c;表示错误时继续下一条。 Sub 提取口味() Set regx CreateObject("vbscript.regexp") On Err…

CodeMirror 如何动态更新definemode

CodeMirror 如何动态更新definemode 问题描述&#xff1a;解决方法&#xff1a; 问题描述&#xff1a; 项目中有一部分用到了CodeMirror组件&#xff0c;其高亮显示的内容需要根据最新的json动态的更新&#xff0c;需要使用definemode自定义高亮内容。 想要的效果如下&#xf…

深度与视差的关系及其转换

深度与视差的关系及其转换 在计算机视觉和立体视觉中&#xff0c;深度和视差是两个重要的概念。理解这两者之间的关系对于实现立体图像处理、三维重建以及深度估计至关重要。在这篇博客中&#xff0c;我们将深入探讨深度和视差的概念&#xff0c;并介绍它们之间的转换关系。 …