第七节 ConfigurationClassParser 源码分析

tips: ConfigurationClassParser 是 Springframework 中的重要类。

本章主要是源码理解,有难度和深度,也枯燥乏味,可以根据实际情况选择阅读。

位置:org.springframework.context.annotation.ConfigurationClassParser

ConfigurationClassParser 它是解密 configuration 的关键。理解 ConfigurationClassParser 对理解整个 Spring 框架至关重要。

一、作用是什么

ConfigurationClassParser是一个非常重要的类,它主要用于解析带有@Configuration注解的类。

@Configuration注解表明该类用作配置类,其中可以定义bean和Spring容器应如何初始化和管理这些bean。

ConfigurationClassParser的作用可以从以下几个方面详细阐述:

  1. 解析导入的配置@Import注解允许一个配置类导入另一个配置类。ConfigurationClassParser解析这些@Import注解,确保所有导入的配置也被处理和应用。
  2. 处理属性注入:通过@PropertySource注解,可以指定一些属性文件,这些属性文件中的属性可以被注入到Spring管理的bean中。ConfigurationClassParser负责解析这些注解,并确保属性文件被加载且其值可用于注入。
  3. 处理@Conditional注解: Spring框架允许在bean的注册过程中使用条件逻辑,@Conditional注解及其派生注解(例如@ConditionalOnClass@ConditionalOnProperty等)使得只有在满足特定条件时,才会进行bean的注册。ConfigurationClassParser负责解析这些条件注解并应用其逻辑。
  4. processDeferredImportSelectors#processImports 处理扩展配置( Starter 能够被处理的核心分支)

二、触发时机

SpringBoot 应用启动过程中,通过后置处理器去触发 ConfigurationClassPostProcessor。 然后再调用 ConfigurationClassParser类解析

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {....
}

处理如下:

下面我们将详细分析源码流程。

三、ConfigurationClassPostProcessor

下面是 ConfigurationClassPostProcessor 部分核心代码。

入口方法 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 。开始分析这段代码。

注意这里有一个 do...while

	do {.....}while (!candidates.isEmpty());

它将逐一识别和解析配置类,然后将配置类中定义的Bean注册到Spring容器中。这个过程通过不断循环直到没有新的配置类候选者出现为止,确保了所有相关的配置都被完整地处理。

// 创建一个配置类解析器,用于解析和处理配置类信息
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 初始化一个集合,用于存储待处理的配置类候选者
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 初始化一个集合,用于跟踪已经解析过的配置类,以避免重复解析
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());// 循环处理,直到没有新的配置类候选者
do {// 解析当前候选者中的配置类parser.parse(candidates);// 对解析结果进行验证parser.validate();// 从解析器中获取已解析的配置类集合Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 移除已经处理过的,避免重复处理configClasses.removeAll(alreadyParsed);// 如果读取器未初始化,创建一个配置类Bean定义读取器if (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 加载并注册配置类中定义的Beanthis.reader.loadBeanDefinitions(configClasses);// 将这批配置类标记为“已解析”alreadyParsed.addAll(configClasses);// 清空候选者集合,为下一轮寻找新候选者做准备candidates.clear();// 检查是否有新的Bean定义被注册(可能由@Configuration类引入)if (registry.getBeanDefinitionCount() > candidateNames.length) {// 重新获取所有Bean定义的名称String[] newCandidateNames = registry.getBeanDefinitionNames();// 创建一个旧候选名称的集合,用于辨识新的候选者Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));// 创建一个集合,用于跟踪已经解析的配置类的类名Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}// 遍历新的Bean定义名称,寻找新的配置类候选者for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}// 更新候选名称列表,以反映新的Bean定义candidateNames = newCandidateNames;}
// 如果还有未处理的候选者,继续循环
} while (!candidates.isEmpty());

特别说明,对于 Bean 的加载和实例化不在本范围了,不进行讲解。感兴趣可以阅读相关章节。

上面的这段代码是 ConfigurationClassPostProcessor 核心。解析来的重头戏。ConfigurationClassParser

四、ConfigurationClassParser

从这行代码开始入手parser.parse(candidates);

  1. parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
  2. processDeferredImportSelectors(); // 处理前面推迟的ImportSelector,一些二方包的导入类,将在这个方法中实现。 例如,我们配置在 Starter Spring.factories 中的自动导入类,将在这一环境被加载
parse方法入口
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {// 如果这个配置类应该根据条件注解被跳过,则直接返回不进行处理if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}// 尝试从已处理的配置类映射中获取这个配置类ConfigurationClass existingClass = this.configurationClasses.get(configClass);// 如果找到了已存在的配置类if (existingClass != null) {// 如果当前处理的是一个导入的配置类if (configClass.isImported()) {// 如果已存在的配置类也是导入的,则合并导入来源if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// 如果已存在的配置类不是导入的,则忽略当前导入的配置类,保留现有的非导入类return;}else {// 如果找到显式的bean定义,可能是意在替换一个导入的类。// 移除旧的配置类,采用新的配置类。this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// 递归处理配置类及其超类层次结构SourceClass sourceClass = asSourceClass(configClass);do {// 处理当前配置类并更新sourceClass为配置类的超类,准备下一轮处理sourceClass = doProcessConfigurationClass(configClass, sourceClass);} while (sourceClass != null); // 如果sourceClass为null,表示超类已经处理完毕// 将处理完的配置类放入配置类映射中,标记为已处理this.configurationClasses.put(configClass, configClass);
}

上面可以理解,解析 MyApplication 类所在工程中的类。最终的解析由 doProcessConfigurationClass 实现

processDeferredImportSelectors

负责处理那些被延迟的特殊接口,使用它来按需动态地导入配置。

这些常常依赖于某些条件才被执行,所以被延迟处理。

// 定义处理延迟的ImportSelector的方法
private void processDeferredImportSelectors() {// 获取之前收集的所有延迟处理的ImportSelectorList<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;// 将引用置为null,表示开始处理过程,防止重复处理this.deferredImportSelectors = null;// 如果没有需要处理的延迟ImportSelector,则直接返回if (deferredImports == null) {return;}...... // 遍历所有的延迟ImportSelectorfor (DeferredImportSelectorHolder deferredImport : deferredImports) {// 获取与当前ImportSelector相关联的配置类ConfigurationClass configClass = deferredImport.getConfigurationClass();try {// 调用ImportSelector的selectImports方法,获取所有的导入类名String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());// 处理这些导入的类,将它们作为配置类进行进一步的处理processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);}}
}

这个方法的主要作用将是找出符合条件的 imports 类。最终还是由processImports() 处理。

到这里,spring.factories 符合条件的一些类将被加载。核心代码deferredImport.getImportSelector().selectImports(configClass.getMetadata());

到这里我们基本上了解了 Starter 是如何被引入进来的。

真正解析的方法 doProcessConfigurationClass

doProcessConfigurationClass

它递归地处理嵌套类、处理@PropertySource注解、处理@ComponentScan注解、处理@Import注解、处理@ImportResource注解、处理@Bean方法、处理接口上的默认方法,最后处理父类

  1. 处理成员类:方法首先递归地处理配置类中定义的任何成员类(嵌套类)。
  2. 处理@PropertySource注解:然后遍历配置类上的所有@PropertySource注解,这些注解用来指明属性文件的位置。如果当前的环境实现了ConfigurableEnvironment接口,则处理注解指定的属性源
  3. 处理@ComponentScan注解:接下来,处理配置类上的所有@ComponentScan注解,这些注解指示Spring扫描特定包下的组件(即带有@Component@Service等注解的类),并注册为Spring容器中的Bean。如果有条件注解指示在此阶段跳过处理,则不执行扫描。
  4. 处理@Import注解:处理配置类上的@Import注解,这些注解用来导入其他配置类或配置选择器,允许模块化地组织配置。
  5. 处理@ImportResource注解:处理配置类上的@ImportResource注解,这些注解用于导入XML配置文件。
  6. 处理@Bean方法:收集配置类中所有带有@Bean注解的方法的元数据,并将它们添加到配置类对象中。这些方法定义了应该由Spring容器管理的Bean。
  7. 处理接口上的默认方法:如果配置类实现了接口,并在这些接口上定义了默认方法,这些方法也会被处理。
  8. 处理父类:最后,如果配置类有超类,那么这个方法会检查超类是否也是一个配置类,不是Java内置类,并且还没有被处理过。如果满足条件,则递归地处理这个超类。

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {// 首先,递归处理任何成员类(嵌套类)processMemberClasses(configClass, sourceClass);// 处理所有的@PropertySource注解for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {// 如果环境实现了ConfigurableEnvironment接口if (this.environment instanceof ConfigurableEnvironment) {// 处理@PropertySource注解processPropertySource(propertySource);} else {// 如果环境没有实现ConfigurableEnvironment接口logger.warn("忽略了[" + sourceClass.getMetadata().getClassName() +"]上的@PropertySource注解。原因:环境必须实现ConfigurableEnvironment接口");}}// 处理所有的@ComponentScan注解Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);// 如果存在@ComponentScan注解,并且当前阶段不应该跳过if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {// 循环处理每个@ComponentScan注解for (AnnotationAttributes componentScan : componentScans) {// 配置类上存在@ComponentScan注解 -> 立即执行扫描Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// 检查扫描结果中的定义集合,如果有进一步的配置类,递归解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());}}}}// 处理所有的@Import注解processImports(configClass, sourceClass, getImports(sourceClass), true);// 处理所有的@ImportResource注解AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);// 如果@ImportResource注解存在if (importResource != null) {// 获取资源位置String[] resources = importResource.getStringArray("locations");// 获取资源的阅读器类Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");// 循环处理每个资源for (String resource : resources) {// 解析资源位置中的占位符String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);// 把解析后的资源添加到配置类configClass.addImportedResource(resolvedResource, readerClass);}}// 处理单独的@Bean方法Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);// 循环处理每个@Bean方法for (MethodMetadata methodMetadata : beanMethods) {// 添加@Bean方法到配置类configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 处理接口上的默认方法processInterfaces(configClass, sourceClass);// 处理父类,如果存在的话if (sourceClass.getMetadata().hasSuperClass()) {// 获取父类名称String superclass = sourceClass.getMetadata().getSuperClassName();// 如果父类存在,并且父类不是java.*开头,并且尚未处理过if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {// 记录已知的父类this.knownSuperclasses.put(superclass, configClass);// 找到父类,返回其注解元数据并递归处理return sourceClass.getSuperClass();}}// 没有父类 -> 处理完成return null;
}

到这里,大体流程我们已经清楚。不再继续针对注解的解析进行讲解,感兴趣可以自行下载源码阅读理解。

五、本章小结

本章是对整 ConfigurationClassParser 进行讲解,它是 Spring framework 中的最核心类。

到这里,Starter 的整个过程已经分析完成,但是针对条件装配,我们将在下一章进行讲解。

 已同步发布到公众号:面汤放盐 第七节 ConfigurationClassParser 源码分析 (qq.com)

掘金账号:第七节 ConfigurationClassParser 源码分析 - 掘金 (juejin.cn)

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

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

相关文章

Java方法的重载

Java方法的重载 前言一、为什么要有重载代码示例问题 代码示例 二、重载的使用代码示例 三、重载的规则针对同一个类代码示例 前言 推荐一个网站给想要了解或者学习人工智能知识的读者&#xff0c;这个网站里内容讲解通俗易懂且风趣幽默&#xff0c;对我帮助很大。我想与大家分…

HTML5好看的通用网站模板源码

文章目录 1.设计来源1.1 主界面1.2 模板菜单1 界面1.3 模板菜单2 界面1.4 模板菜单3 界面1.5 下拉菜单1 界面1.6 下拉菜单2 界面1.7 模板菜单4 界面1.8 模板菜单5 界面1.9 界面底部 2.效果和源码2.1 动态效果2.2 源码目录2.3 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址…

django-celery-beat自动调度异步任务

Celery是一个简单、灵活且可靠的分布式系统&#xff0c;专门用于处理大量消息的实时任务调度。它支持使用任务队列的方式在分布的机器、进程、线程上执行任务调度。Celery不仅支持异步任务&#xff08;如发送邮件、文件上传、图像处理等耗时操作&#xff09;&#xff0c;还支持…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

记录Python低代码开发框架zdppy_amcrud的开发过程

实现新增接口 基础代码 import env import mcrud import api import snowflakeenv.load(".env") db mcrud.new_env()table "user" columns ["name", "age"]async def add_user(req):data await api.req.get_json(req)values [d…

跟TED演讲学英文:Do schools kill creativity by Sir Ken Robinson

Do schools kill creativity? Link: https://www.ted.com/talks/sir_ken_robinson_do_schools_kill_creativity Speaker: Sir Ken Robinson Date: February 2006 文章目录 Do schools kill creativity?IntroductionVocabularySummaryTranscriptAfterword Introduction Sir…

[AI Google] 10个即将到来的Android生态系统更新

新的体验带来了更强的防盗保护、手表电池寿命优化&#xff0c;以及对电视、汽车等的娱乐功能改进。 昨天&#xff0c;我们分享了Android如何以人工智能为核心重新构想智能手机。今天&#xff0c;我们推出了Android 15的第二个测试版&#xff0c;并分享了更多我们改进操作系统的…

webSocket+Node+Js实现在线聊天(包含所有代码)

这篇文章主要介绍了如何使用 webSocket、Node 和 Js 实现在线聊天功能。 重要亮点 &#x1f4bb; 技术选型&#xff1a;使用 Node.js 搭建服务器&#xff0c;利用 Express 框架和 Socket.io 库实现 WebSocket 通信。 &#x1f4c4; 实现思路&#xff1a;通过建立数组存储聊天…

Linux 软件包管理器 yum的下载、功能介绍及使用

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 Linux下的三种软件安装方…

项目如何有效做资源管理?易趋项目管理软件让资源管理可视化

在项目管理的过程中&#xff0c;有效的资源管理能够确保资源得到合理的分配和使用&#xff0c;避免资源的浪费和冗余&#xff0c;进而提高整体工作效率、确保项目的成功&#xff1b;同时降低组织的运营成本。 但在项目推进过程中&#xff0c;项目经理总会面临各种资源管理的难…

MyBatis从入门到“入土“

&#x1f495;喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#xff01;&#x1f495;(●◡●) 目录 一、Mybatis为何物&#xff1f;&#x1f44c; 二、快速入门&#x1f923; 1、新建项目&#x1f60a; 2、数据库建表&#x1f60a; 3、导入依赖的jar包&#x1f60a;…

vue3 路由跳转 携带参数

实现功能&#xff1a;页面A 跳转到 页面B&#xff0c;携带参数 路由router.ts import { createRouter, createWebHistory } from "vue-router";const routes: RouteRecordRaw[] [{path: "/demo/a",name: "aa",component: () > import(&quo…

【软件设计师】2018年的上午题总结

2018 2018上半年2018下半年 2018上半年 1.小阶向大阶对齐 2.吞吐率是最长流水段操作时间的倒数 3.ssh的端口号是22 4.s所发送的信息使用s的私钥进行数字签名&#xff0c;t收到后使用s的公钥验证消息的真实性 5.数据流分析是被动攻击方式 6.《计算机软件保护条例》是国务院颁布…

226.翻转二叉树

翻转一棵二叉树。 思路&#xff1a; 指针做交换 用递归&#xff08;前序or后序&#xff0c;中序不行&#xff09; 前序&#xff1a;中左右 遍历到“中”的时候&#xff0c;交换它的左右孩子 然后分别对它的左孩子和右孩子使用“交换函数”&#xff08;定义的&#xff09;&a…

【论文阅读】使用深度学习及格子玻尔兹曼模拟对SEM图像表征粘土结构及其对储层的影响

文章目录 0、论文基本信息1、深度学习2、可运行程序—Matlab3、深度切片3、LBM模拟4、局限性 0、论文基本信息 论文标题&#xff1a;Characterizing clay textures and their impact on the reservoir using deep learning and Lattice-Boltzmann simulation applied to SEM i…

Python-温故知新

1快速打开.ipynb文件 安装好anaconda后&#xff0c;在需要打开notebook的文件夹中&#xff0c; shift键右键——打开powershell窗口——输入jupyter notebook 即可在该文件夹中打开notebook的页面&#xff1a; 2 快速查看函数用法 光标放在函数上——shift键tab 3...

CGAN|生成手势图像|可控制生成

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;TensorFlow入门实战&#xff5c;第3周&#xff1a;天气识别&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 CGAN&#xff08;条件生成对抗网络&#xf…

【Crypto】Rabbit

文章目录 一、Rabbit解题感悟 一、Rabbit 题目提示很明显是Rabbit加密&#xff0c;直接解 小小flag&#xff0c;拿下&#xff01; 解题感悟 提示的太明显了

二分查找

题目链接 题目: 分析: 如果按照从头到尾的顺序一次比较, 每次只能舍弃一个元素, 效率是非常低的, 而且没有用到题目的要求, 数组是有序的因为数组是有序的, 所以如果我们随便找到一个位置, 和目标元素进行比较, 如果大于目标元素, 说明该位置的右侧元素都比目标元素大, 都可…

一键恢复安卓手机数据:3个快速简便的解决方案!

安卓手机作为我们不可或缺的数字伙伴&#xff0c;承载着大量珍贵的个人和工作数据。然而&#xff0c;随着我们在手机上进行各种操作&#xff0c;不可避免地会遇到一些令人头痛的问题&#xff0c;比如意外删除文件、系统故障或其他不可预见的情况&#xff0c;导致重要数据的丢失…