Spring 中 ConfigurationClassPostProcessor 类扫描解析之 @ComponentScan 解析

news2025/2/2 10:52:59

ConfigurationClassPostProcessor 简单概述

Spring 中类的解析是非常重要的,因为工程中有很多类,并且被一些注解修饰,比如:@Component@Bean@Import@PropertySource@ImportSource@Scope 等。

你在类或者方法上标注这些注解,Spring 想要认识它,就需要通过 ConfigurationClassPostProcessor 类去解析扫描,以一定的形式加载到 Spring 中 (BeanDefinition),这样 Spring 才能够去使用它,并且去管理它。

ConfigurationClassPostProcessor 类是 BeanFactoryPostProcessor 接口的应用,在同类行中它的执行优先级是最低的,它的作用就是将工程中的类 xxx.class 文件解析封装成 BeanDefinition,然后 Spring 就可以根据 BeanDefinition 模版生产 bean。

可以说没有 ConfigurationClassPostProcessor 这个类就没有 Spring 大家族,一切都是靠这个类白手起家的!

ConfigurationClassPostProcessor 类注册和调用

那么 ConfigurationClassPostProcessor 类是设么时候存在的呢?追踪源码如下:

在这里插入图片描述在这里插入图片描述

从上面源码中可以看出 Spring 直接内置了这几个核心类,直接手动创建 RootBeanDefinition 封装 ConfigurationClassPostProcessor 的模版。因为 Spring 肯定要有带头大哥,这几个内置的系统类都是带头大哥,这样就可以通过一个类然后生产一大家族出来。

那么 BeanDefinition 已经创建好啦,在哪个地方将 ConfigurationClassPostProcessor 实例化的呢?源码如下:

在这里插入图片描述在这里插入图片描述

最终追踪到下面这段源码中,Spring 通过 getBeanNamesForType() 方法获取到所有实现 BeanDefinitionRegistryPostProcessor 接口的子类实现,其实目前只有一个那就是 ConfigurationClassPostProcessor,因为现在才刚开始阶段,类的解析都还没开始,所以 Spring 容器中基本上没啥东西。这里直接调用 getBean() 实例化 ConfigurationClassPostProcessor 类。

在这里插入图片描述

然后下面就要开始调用 ConfigurationClassPostProcessor 这个类中的 postProcessBeanDefinitionRegistry() 方法去解析扫描其他类,就此类的解析流程就开始了!后面都是围绕着这个类进行解析。

在这里插入图片描述在这里插入图片描述

@ComponentScan 扫描解析类

进入到 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry() 方法中,我们先看比较简单的 @ComponentScan 注解扫描解析。

@ComponentScan 注解大家都知道会去扫描 @Component 修饰的类,最终交给 Spring 管理。这里就用一个案例开始分析。

先定义个 Student 类,并且用 @Component 修饰,如下:

package com.gwm.componentscan;

@Component
public class Student {

}

然后定义一个测试类,并且通过 @ComponentScan 注解去扫描 @Component 修饰的类,这里直接指定 basePackages 包的路径,如果不指定包路径,也会默认使用 ComponentScanTest 类所在的包路径(后面源码可以看到这个逻辑)作为扫描路径, 如下:


@ComponentScan(basePackages = "com.gwm.componentscan")
public class ComponentScanTest {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanTest.class);
		Student bean = context.getBean(Student.class);
		System.out.println("bean = " + bean);
	}
}

这里 ComponentScanTest 类作为 AnnotationConfigApplicationContext 类的参数,即为入口类,这是第一个自定义类被注册到 BeanDefinitionMap 中,源码如下:

在这里插入图片描述

Spring 手动创建一个 AnnotatedGenericBeanDefinition 模版,封装 ComponentScanTest.class 类,然后最终注册到 Spring 容器的 BeanDefinitionMap 中。

在这里插入图片描述在这里插入图片描述

这个 ComponentScanTest 类将会成为导火索,由这个类触发可以解析更多的类,注册完入口类之后,回到
ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry() 方法中,核心源码如下:

在这里插入图片描述

从上述源码中可以看出,ComponentScanTest 类被 @ComponentScan 修饰,所以会加入到 configCandidates 容器中,等待被处理,接着往下看,准备好一个专门用来解析类内容的对象 ConfigurationClassParser,源码如下:

在这里插入图片描述

判断 ComponentScanTest 类是否有 @ComponentScan 注解,很显然是有的,并且获取到 @ComponentScan 所有的属性信息,其中一个就是 basePackages 包路径,源码如下:

在这里插入图片描述

然后进入 parse() 方法开始解析,进来就直接生成一个包扫描器 ClassPathBeanDefinitionScanner ,这个类负责把 Spring 中指定包下面的所有被 @Component 修改的类扫描到 Spring 中

在这里插入图片描述

进入 ClassPathBeanDefinitionScanner 包扫描器构造方法中,默认会给包扫描器一个过滤条件,其实就是指定这个包扫描器只能对 @Component 生效,源码如下:

在这里插入图片描述

指定只扫描有 @Component 注解修饰的类,所以为什么 @ComponentScan 默认只扫描 @Component 注解,其他注解都不管,原因就是这个过滤条件限定死了。

在这里插入图片描述

下面这段逻辑就解释了为什么你在 @ComponentScan 中不用显示的配置包路径也能扫描到类,Spring 默认就会用入口类所在的包路径作为扫描路径,源码如下:

在这里插入图片描述

然后进入 doScan() 方法,开始真正的扫描,源码如下:

在这里插入图片描述
在这里插入图片描述

将 doScan() 方法拆分成三部分讲解:

第一部分: findCandidateComponents() 内部逻辑就是通过 Resource 流的方式去这个包路径下面读取所有的 class 文件,然后判断哪些 class 上面有 @Component 注解修饰,有的话,就会封装成 ScannedGenericBeanDefinition 模版,注意这里只是封装,而且 BeanDefinition 的属性还比较少,并没有注册到 Spring 容器中。

进入 findCandidateComponents() 方法,源码如下:

在这里插入图片描述

过滤是否有 @Component 修饰的类,includeFilters 在前面已经提过,Spring 默认就给这个容器中放了一个 @Component 注解,表示只处理又这个注解修饰的类。源码如下:

在这里插入图片描述

第二部分: 第一步封装的 BeanDefinition 属性还比较少,现在继续解析 class 上是否有 @Scope 注解修饰,然后生成 beanName 名称,也是唯一的 bean 的 ID,然后就是其他 BeanDefinition 属性的填充(@Lazy、@Primary 等),这第二部完成,BeanDefinition 属性就立马多起来了。

第三部分: 将第二步完善好的 BeanDefinition 注册到 Spring 容器中 (BeanDefinitionMap、BeanDefinitionNames)

经过这三部操作,@ComponentScan 注解算基本上完成对 @Component 扫描解析,但是还不够全,因为你不知道解析出来的类里面是否还包含 @Component 注解或者其他注解 @Import 等,所以需要继续进行判断,看解析完的的类里面是否还包含 @Component、@Import 等注解,追踪源码如下:

在这里插入图片描述

注意这个判断条件 checkConfigurationClassCandidate(),这个判断条件有点意思,有什么作用呢?就是在这个方法里面如果判断类是被 @Configuration 注解修饰,就会认为是全扫描,并标记上为 full 全扫描,被 @Component 注解修饰的,认为是部分扫描,并标记为 little 部分扫描。 其实打上这个标记就是在后续操作中 full 标记的类会用代理的方式生成对象,侧面说明 @Configuration 注解修饰的类最终 Spring 容器中是一个代理对象,而且是 cglib 代理对象,这个在这里只是扩展,具体可以看我另一篇文章有解析过,现在继续回到这里。

在这里插入图片描述

发现代码会继续调用 parse() 方法去扫描解析 @Component、和其他注解修饰的类,其实这是一种递归调用。

最终解析完后 @Component 修饰的类都被 @ComponentScan 全部解析完成,并注册到 Spring 容器中。

总结

读取所有资源文件判断哪些有 @Component 修饰,封装成 BeanDefinition,然后注册到 Spring 容器中完事

扩展

MetadataReader 元数据封装着一个类所有的内容,另一篇文章中有详细介绍
ClasspathBeanDefinitionScanner 包扫描器,对指定路径下进行扫描,过滤条件可以自己重写 Filter

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

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

相关文章

Tensorflow1 搭建Cuda11

前言 Tensorflow1中默认支持cuda10及以下的,最高的版本Tensorflow1.15默认使用cuda10;但是一些高性能的显卡,比如A100、3090等,它们只支持Cuda11的,这就不太友善了,毕竟不少项目依赖Tensorflow1搭建的。 …

linux基础学习-系统信息相关命令以及一些其他命令

主要是为了方便远程终端维护服务器时,查看服务器上当前的 系统日期和时间/磁盘空间占用情况/程序执行命令时间和日期 磁盘信息 -h:以人性化的方式显示文件大小进程信息 进程通俗地说就是当前正在执行的一个程序 ps默认只会显示当前用户通过终端启动的…

使用 openpyxl 在 Excel 电子表格中自动执行日常任务

花费数小时处理 Excel 中平凡的重复性任务。使用 Python 和 openpyxl 探索自动化。 像许多从事各种业务职能的人一样,我几乎每天都使用 Excel 来制作图表和分析。然而,其中一些图表需求相当平凡,涉及使用新的原始数据集创建新的工作表,该数据集需要以某种可预测的形式进行处…

采购管理基础知识:采购方法、模式与数据的作用

采购是任何企业的一个关键部分,是从外部来源获得货物和服务的过程。采购的目标是为企业获得尽可能好的价值。 采购流程可以通过使用各种工具进行优化。最重要的工具是采购管理软件。这种软件可以帮助企业跟踪他们的采购过程,并确保它们是有效的。 采购…

javaweb当中mysql要掌握的知识点(简单基础入门)

目录 1.mysql的数据模型 A.关系型数据库 B.mysql的客户端和数据库间的关系 C.数据库当中不同文件控制表的相关数据 2.使用注释 A.使用井号#加上要注释的文字 3.DDL---操作数据库 A.基本操作 B.创建表 C.修改表 D.给表添加数据 E.基础查询 F.排序查询语法 G.分页查询 4.…

linux下的环境变量

环境变量查看环境变量方法环境变量的组织方式通过代码如何获取环境变量 查看环境变量方法 环境变量的组织方式通过代码如何获取环境变量查看环境变量方法 环境变量的组织方式 通过代码如何获取环境变量 查看环境变量方法 ./常见的环境变量. 1️⃣ PATH:指定命令的搜索路径 2…

有哪些好用的建筑工程项目管理软件?

工程项目管理是建筑企业经营管理的核心业务,任何一家施工企业、任何一个工程管理团队, 无论是建立初创型企业,还是小公司开始进行业务扩张时,工程项目管理都至关重要。 针对行业痛点,推荐试试这款系统:ht…

银河麒麟V10系统NetworkManager服务启动失败的解决方法

目录 一、NetworkManger网络服务启动失败 二、故障定位过程 (一)重装NetworkManager未解决 (二)重装openssl未解决 三、解决方案 (一)修改/etc/ld.so.conf配置文件 (二)执行ld…

实操干货!专利的12种用处。

对于专利申请人和企业来说,为什么要申请专利以及申请到的专利能有什么作用? 答案可能会有很多种,小编在本文中列举了12种。你的理由是什么呢?可以在文后留言与我们进行互动交流。 几十年来中国专利申请数量已足足有几百万&#x…

C语言 CJSON使用实例

C语言 CJSON使用实例 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录C语言 CJSON使用实例前言一、cJSON实例1. json数据的封装2. json数据解析前言 提示:这里可以添加本文要记录的大概内容&#xff1a…

【小5聊】Sql Server时间转换和查询时间范围查询不正确的原因

最近在做时间方法封装的时候发现了一个问题! 如果sql语句输出的时间字段转为了字符串输出,那么在使用此字段作为时间范围筛选时发现无效了,没法过滤对应的时间范围内记录 下面进行场景重现下 1、创建表 创建只有三个字段的表testTable&…

LeetCode:1760. 袋子里最少数目的球

解题思路: 看了很久也不知道该怎么下手,果断转去题解看答案,所实话官方的题解说的有些抽象,先买那是我自己看了别人的博客和思考后的一些思路: 1、为什么可以用二分查找? 题目要求你的开销是单个袋子里球…

滴滴前端常考vue面试题整理

谈谈对keep-alive的了解 keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性 include/exclude ,2个生命周期 activated , deactivated vue和react的区别 > 相同点: 1. 数据驱动页面&#x…

jQuery DOM

文章目录jQuery DOM概述操作元素创建元素插入节点prepend()prependTo()append()appendTo()before()insertBefore()after()insertAfter()删除元素remove()detach()empty()复制元素clone()替换元素replaceWith()replaceAll()包裹元素wrap()wrapAll()wrapInner()遍历元素属性操作获…

Iceberg在袋鼠云的探索及实践

“数据湖”、“湖仓一体”及“流批一体”等概念,是近年来大数据领域热度最高的词汇,在各大互联网公司掀起了一波波的热潮,各家公司纷纷推出了自己的技术方案,其中作为全链路数字化技术与服务提供商的袋鼠云,在探索数据…

微服务框架 SpringCloud微服务架构 微服务面试篇 54 微服务篇 54.5 Nacos与Eureka的区别有哪些?【接口方式、实例类型、健康检测】

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务面试篇 文章目录微服务框架微服务面试篇54 微服务篇54.5 Nacos与Eureka的区别有哪些?【接口方式、实例类型、健康检测】54…

【大数据入门核心技术-Kafka】(七)Kafka扩容broker和数据迁移

目录 一、准备工作 1、安装好Zookeeper集群 2、安装好Kafka集群 二、Kafka扩容broker 三、Kafka数据迁移 1、查看主题列表 2、创建Topic 3、查看Topic详细信息 4、生成需要迁移的json 5、生成迁移计划 6、执行迁移计划 7、查看迁移计划 8、确认topic数据分布 一、…

Shiro框架学习笔记、整合Springboot、redis缓存

本笔记基于B站UP主不良人编程 目录 1.权限的管理 1.1什么是权限管理 1.2什么是身份认证 1.3什么是授权 2.什么是Shiro 3.Shiro的核心架构 3.1 S核心内容 4.shiro中的认证4.1认证 4.2shiro中认证的关键对象 4.3认证流程 4.4认证程序开发流程 4.4认证程序源码 4.5自定…

java ssm羽毛球馆管理和交流平台系统

羽毛球作为每个人爱好的一项体育运动,越来也收到人们的好评和关注。很多羽毛球爱好者通过网站的形式对羽毛球场地情况,羽毛球的爱好者的互相学习进行交流,方便了大众对于羽毛球的交流和沟通,提高羽毛球技术的同时,也让…

颠覆传统返利模式,针对用户复购率低的全新解决方案——消费盲返

如今互联网商业模式遍地开花,谈及商业模式,大家第一想到的肯定是积分消费返利,那么“消费返利”对于大家来说都不陌生,那么本期林工想给大家介绍的是一个怎么听怎么亏,实则已经悄悄爆火的模式——消费盲返,…