Spring源码篇(九)自动配置扫描class的原理

news/2024/4/28 17:59:12/文章来源:https://blog.csdn.net/qq_28911061/article/details/132113128

文章目录

  • 前言
  • ClassLoader
  • 如何加载jar包里的class
  • 自动配置扫描class的原理
    • spring中的加载方式
    • 源码
    • 总结

前言

spring是怎样通过@ComponentScan,或者自动配置扫描到了依赖包里class的?

ClassLoader

这里涉及到了class Loader的机制,有些复杂,jdk中提供默认3个class Loader:

  • Bootstrap ClassLoader:加载jdk核心类库;加载%JAVA_HOME\lib%下的jar;
  • ExtClassLoader:加载jdk扩展类库;加载%JAVA_HOME\lib\ext%下的jar;
  • AppClassLoader:加载classpath下的class,以及关联到maven仓库里的jar;

AppClassLoaderExtClassLoader父类都是URLClassLoader,我们自定义也是继承URLClassLoader进行扩展的;

所以,当我们使用类加载器加载资源时,它会找上面这些路径,而AppClassLoader是找当前执行程序的classpath,也就是我们target/classes目录,如果有是maven引用了其他依赖包,那么也会将maven地址下的依赖包的路径加到AppClassLoaderURL里,如果是多模块的项目,还会把引用的其他模块下target/classes的目录也加进来。

image-20230804144522488

image-20230804144419885

如何加载jar包里的class

假设需要获取一个jar包里的class该如何?

如下4个步骤即可:

    public static void main(String[] args) throws Exception {String packageName = "com.liry.springplugin";// 1. 转换为 com/liry/springpluginString packagePath = ClassUtils.convertClassNameToResourcePath(packageName);// 2. 通过类加载器加载jar包URL
//        ClassLoader classLoader = Test.class.getClassLoader();ClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:E:\\workspace\\git\\test-plugin\\spring-plugin\\target\\spring-plugin-1.0-SNAPSHOT.jar")});URL resources = classLoader.getResource(packagePath);// 3. 打开资源通道JarFile jarFile = null;URLConnection urlConnection = resources.openConnection();if (urlConnection instanceof java.net.JarURLConnection) {java.net.JarURLConnection jarURLConnection = (java.net.JarURLConnection) urlConnection;jarFile = jarURLConnection.getJarFile();}// 定义一个结果集List<String> resultClasses = new ArrayList<>();// 4. 遍历资源文件Enumeration<JarEntry> entries = jarFile.entries();while (entries.hasMoreElements()) {JarEntry entry = entries.nextElement();// 文件全路径String path = entry.getName();// 判断是否在指定包路径下,jar包里有多层目录、MF文件、class文件等多种文件信息if (path.startsWith(packagePath)) {// 使用spring的路径匹配器匹配class文件if (path.endsWith(".class")) {resultClasses.add(path);}}}resultClasses.forEach(System.out::println);}

image-20230803174544910

说明一下,加载jar包的问题;

上面给出了两种方式

第一种:使用类加载加载

ClassLoader classLoader = Test.class.getClassLoader();

第二种:使用URLClassLoader加载

ClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:E:\\workspace\\git\\test-plugin\\spring-plugin\\target\\spring-plugin-1.0-SNAPSHOT.jar")});

这两种方式不同之处在于,查找jar的路径,第一种方式因为我测试项目使用的maven,在pom.xml里引入了spring-plugin-1.0-SNAPSHOT的包,所以才能通过类加载器直接进行加载,这是因为使用maven,maven引用的依赖路径会被加入到AppClassLoader种,然后使用Test.class.getClassLoader()去加载class时,会委派给AppClassLoader进行加载,才会加载到。

所以,如果不是在maven种引入的包,使用第二种方式。

自动配置扫描class的原理

那么这里简单的走一下自动配置流程:

  1. 启动类上有@EanbleConfiguration,会读取META-INF/spring.factories里配置的配置类
  2. 读取后解析成beanDefinition,然后判断是否配置类,如果是就找配置类注解(@ComponentScan @Import @Component @Service等这样的注解),如果配置类有扫描class的注解,就去扫描
  3. 最后得到所有的bean的beanDefinition

spring中的加载方式

在spring中加载class的方式就是上面的方式,我这里就在上面示例的基础上增加一些细节,如下:

 static PathMatcher pathMatcher = new AntPathMatcher();public static void getClassResource() throws Exception {String packageName = "com.liry.springplugin";// 1. 转换为 com/liry/springpluginString packagePath = ClassUtils.convertClassNameToResourcePath(packageName);// 2. 通过类加载器加载jar包URLClassLoader classLoader = Test.class.getClassLoader();URL resources = classLoader.getResource(packagePath);// spring的资源文件对象UrlResource rootResource = new UrlResource(resources);// 3. 打开资源通道JarFile jarFile = null;URLConnection urlConnection = resources.openConnection();if (urlConnection instanceof java.net.JarURLConnection) {java.net.JarURLConnection jarURLConnection = (java.net.JarURLConnection) urlConnection;jarFile = jarURLConnection.getJarFile();}// 定义一个结果集List<Resource> resultClasses = new ArrayList<>();// 4. 遍历资源文件Enumeration<JarEntry> entries = jarFile.entries();// 包路径以 / 结尾拥于后面进行替换if (packagePath.endsWith("/")) {packagePath += "/";}while (entries.hasMoreElements()) {JarEntry entry = entries.nextElement();// 文件全路径String path = entry.getName();// 判断是否在指定包路径下,jar包里有多层目录、MF文件、class文件等多种文件信息if (path.startsWith(packagePath)) {// 这里去掉指定的包名,比如com/liry/springplugin/AutoConfig.class,结果就是AutoConfig.classString relativePath = path.substring(packagePath.length() + 1);// 使用spring的路径匹配器匹配class文件if (pathMatcher.match("**/*.class", relativePath)) {Resource relative = rootResource.createRelative(relativePath);resultClasses.add(relative);}}}resultClasses.forEach(d -> System.out.println(d.getFilename()));}

image-20230804132300035

上面这段已经和spring中加载class的方式是一样的了,对应源码位置:

org.springframework.core.io.support.PathMatchingResourcePatternResolver#doFindPathMatchingJarResources

源码

如果配置类存在@ComponentScan,会拿到注解里的值,也就是basePackages,然后走到:

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

这里就是扫描所有的class,然后再构建出beanDefinition对象

image-20230804135544195

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

image-20230804141622499

image-20230804141650001

org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources

image-20230804141739226

最后走到这里,这里就是读取class的地方,这里的逻辑就和上面的例子是一样的

org.springframework.core.io.support.PathMatchingResourcePatternResolver#doFindPathMatchingJarResources

image-20230804142148041

这一步就是在匹配@ComponentScan的basePackages下的class。

总结

  1. 当前项目路径会被加载到AppClassLoader,而且使用maven,对应的maven里的jar路径也会被加载到AppClassLoader中;
  2. 注册配置类,启动类为入口;
  3. 通过配置类找到找到@ComponentScan扫描指定路径class,如果找到配置类,还有@Import开头的注解,以及@EnableConfiguration这些注解,也都是把class找到,然后判断是否配置类,如果是就再去找这些注解,以此循环;
  4. 如果是@EnableConfiguration注解,读取META-INF/spring.factories文件里的value,并解析成配置类,再循环;通样的如果是@Import注解,引入的是一个非DeferredImportSelector的配置类也是如此,
  5. 最后项目中就存在所有的bean的beanDefinition

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

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

相关文章

达芬奇架构 DaVinci Core - 小记

文章目录 官方文档 &#xff1a; HUAWEI Da Vinci Architecture https://support.huaweicloud.com/intl/en-us/odevg-A800_9000_9010/atlaste_10_0007.htmlPPT : DaVinci: A Scalable Architecture for Neural Network Computing https://www.cmc.ca/wp-content/uploads/2020/0…

一篇文章带你基本了解Java 集合框架、核心接口、以及需要掌握的各个数据结构

一篇文章带你基本了解Java 集合框架 基本概念&#xff1a; ​ 早在 Java 2 中之前&#xff0c;Java 就提供了特设类。比如&#xff1a;Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。 ​ Java集合框架&#xff08;Java Collections Framework&…

Pytorch Tutorial【Chapter 2. Autograd】

Pytorch Tutorial 文章目录 Pytorch TutorialChapter 2. Autograd1. Review Matrix Calculus1.1 Definition向量对向量求导1.2 Definition标量对向量求导1.3 Definition标量对矩阵求导 2.关于autograd的说明3. grad的计算3.1 Manual手动计算3.2 backward()自动计算 Reference C…

极光笔记 | 浅谈企业级SaaS产品的客户成长旅程管理(上)—— 分析篇

本文作者&#xff1a;陈伟&#xff08;极光用户体验部高级总监&#xff09; “企业级SaaS产品与C端互联网产品特征差异很大&#xff0c;有些甚至是截然相反&#xff0c;这些特征也会成为后续客户成长旅程的重要影响变量。本文就如何设计并服务好企业级SaaS产品客户成长旅程进行…

全网最强,Python接口自动化测试实战-接口参数关联(购物实例)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么是参数关联&a…

Spring之浅谈AOP技术

目录 前言 1.AOP的作用 2.AOP核心 Spring实现AOP 3.AOP工作流程 4.AOP核心概念 5.AOP通知类型 5.1类型介绍 5.2通知类型的使用 前置通知 后置通知 ​​​​​​​环绕通知 前言 AOP&#xff1a;Aspect Oriented Programming&#xff08;面向切面编程&#xff09;&…

收藏!9款好用的前端可视化工具推荐

“可视化开发”是上个世纪90年代软件界最大的热点之一。 当初&#xff0c;可视化开发主要专注于用户界面的构建&#xff0c;让开发者通过简单的拖拽操作&#xff0c;快速搭建用户界面&#xff0c;一些成熟产品更是实现了“所见即所得”。在与当时最先进的高级编程语言相比较时&…

01-序言

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan 简介&#xff1a; 此专栏是学习“线性代数”课程做的笔记&#xff0c;教程来自B站的3Blue1Brown​​​​​​​d​​​​​​​。 视频作者是Grant Sanderson&#xff0c; 他本人是斯坦…

Redis两种持久化方案RDB持久化和AOF持久化

Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启…

【ASP.NET MVC】使用动软(三)(11)

一、问题 上文中提到&#xff0c;动软提供了数据库的基本操作功能&#xff0c;但是往往需要添加新的功能来解决实际问题&#xff0c;比如GetModel&#xff0c;通过id去查对象&#xff1a; 这个功能就需要进行改进&#xff1a;往往程序中获取的是实体的其他属性&#xff0c;比如…

Vue2 第十八节 插槽

1.默认插槽 2.具名插槽 3.作用域插槽 插槽 ① 作用&#xff1a;让父组件可以向子组件指定位置插入html结构&#xff0c;也是一种组件间通信的方式&#xff0c;适用于父组件和子组件间通信 ② 分类&#xff1a;默认插槽&#xff0c;具名插槽&#xff0c;作用域插槽 一.默认…

【Linux】创建与删除用户

新增用户&#xff1a; adduser 用户名【添加用户】 passwd 用户名【设置用户密码】删除用户&#xff1a; userdel -r 用户名【删除用户】

【机器学习】在 MLOps构建项目 ( MLOps2)

My MLOps tutorials: Tutorial 1: A Beginner-Friendly Introduction to MLOps教程 2&#xff1a;使用 MLOps 构建机器学习项目 一、说明 如果你希望将机器学习项目提升到一个新的水平&#xff0c;MLOps 是该过程的重要组成部分。在本文中&#xff0c;我们将以经典手写数字分类…

【力扣】206. 反转链表 <链表指针>

【力扣】206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例 3 输入&#xff1a…

Qt中ffmpeg API存储和显示摄像头视频

Qt中ffmpeg API存储和显示摄像头视频的功能需要之前写的视频ffmpegAPI的视频播放的流程。 代码源码位置&#xff1a;https://download.csdn.net/download/qq_43812868/88157743?spm1001.2014.3001.5503 一、存储和显示摄像头的视频的流程 这是读取打开视频文件的流程&#x…

8.4作业

用信号量的方式实现打印1234567后打印7654321循环交替打印。 #include<stdio.h> #include<string.h> #include<stdlib.h> #include<head.h> char buf[]"1234567"; sem_t sem; void *callBack1(void *arg) {int i0;int sstrlen(buf)-1;while…

LeetCode 27题:移除元素

题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长…

FreeIPA Server/Client不同版本组合,对podman rootless container的支持

FreeIPA Server/Client不同版本组合&#xff0c;对podman rootless container的支持 根据实验&#xff0c; CentOS 7.9 yum仓库自带的FreeIPA Server 4.6.8&#xff0c; ipa client版本支持CentOS 7.9 yum仓库自带的FreeIPA Client 4.6.8不支持subids&#xff0c;podman调用…

NVIDIA 535.86.05 Linux 图形驱动程序改进 Wayland 支持

NVIDIA公司近日发布了适用于 Linux、FreeBSD 和 Solaris 系统的 NVIDIA 535.86.05 图形驱动程序&#xff0c;作为其生产分支的维护更新&#xff0c;解决了各种错误和问题。 NVIDIA 535.86.05 是在 NVIDIA 535.54.03 发布一个多月之后发布的&#xff0c;它通过解决在使用某些 W…

G-channel 实现低光图像增强

G-channel 之前研究低光图像增强时&#xff0c;看到一篇博客&#xff0c;里面介绍了一种方法&#xff0c;没有说明出处&#xff0c;也没有说明方法的名字&#xff0c;这里暂时叫做 G-channel 算法。 博客地址&#xff1a;低照度图像增强&#xff08;附步骤及源码&#xff09;…