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

文章目录

  • 前言
  • 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/springplugin
        String 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/springplugin
        String packagePath = ClassUtils.convertClassNameToResourcePath(packageName);

        // 2. 通过类加载器加载jar包URL
        ClassLoader 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.class
                String 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

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

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

相关文章

广州银行信用卡中心:强化数字引擎安全,实现业务稳步增长

广州银行信用卡中心是全国城商行中仅有的两家信用卡专营机构之一&#xff0c;拥有从金融产品研发至销售及后期风险控制、客户服务完整业务链条&#xff0c;曾获“2016年度最佳创新信用卡银行”。 数字引擎驱动业务增长 安全左移降低开发风险 近年来&#xff0c;广州银行信用卡…

SpringCloud之微服务API网关Gateway介绍

文章目录 1 微服务API网关Gateway1.1 网关简介1.2 Spring Cloud Gateway介绍1.3 Gateway特性1.4 Gateway核心概念1.4.1 路由1.4.1.1 定义1.4.1.2 动态路由 1.4.2 断言1.4.2.1 默认断言1.4.2.2 自定义Predicate 1.4.3 过滤器1.4.3.1 默认过滤器1.4.3.2 自定义Filter&#xff08;…

【CI/CD】图解六种分支管理模型

图解六种分支管理模型 任何一家公司乃至于一个小组织&#xff0c;只要有写代码的地方&#xff0c;就有代码版本管理的主场&#xff0c;初入职场&#xff0c;总会遇到第一个拦路虎 git 管理流程&#xff0c;但是每一个企业似乎都有自己的 git 管理流程&#xff0c;倘若我们能掌握…

redis初级

Redis 课程内容 Redis入门Redis数据类型Redis常用命令在Java中操作RedisRedis持久化机制 1. Redis入门 1.1 Redis简介 Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。 **官网&#xff1a;**https://redis.io **中文网&…

【IC设计】ICC workshop Lab1 数据准备基本流程 【脚本总结】

Task 1 Create a Milkyway library 先进入lab1_data_setup目录&#xff0c;打开icc_shell&#xff0c;创建项目 create_mw_lib -technology $tech_file -mw_reference_library "$mw_path/sc $mw_path/io $mw_path/ram16x128" -bus_naming_style {[%d]} -open $my_m…

C5.0决策树建立个人信用风险评估模型

通过构建自动化的信用评分模型&#xff0c;以在线方式进行即时的信贷审批能够为银行节约很多人工成本。本案例&#xff0c;我们将使用C5.0决策树算法建立一个简单的个人信用风险评估模型。 导入类库 读取数据 #创建编码所用的数据字典 col_dicts{} #要编码的属性集 cols [che…

conda install 和pip install有什么区别?

本篇为分享贴&#xff0c;截图部分选自知乎&#xff0c;部分选自csdn&#xff0c;文字内容是结合自己实践进行总结。 环境引用的包在哪&#xff1f; 首先&#xff0c;一条命令&#xff1a; python -m site 这条命令可以定位引用的包在哪里 &#xff0c;当然也可以自己设置默认…

k8s存储卷

目录 一、为什么要存储卷&#xff1f;二、emptyDir存储卷三、hostPath存储卷四、 nfs共享存储卷五、PVC 和 PV5.1 PV和PVC之间的相互作用遵循的生命周期5.2 PV 的状态5.3 一个PV从创建到销毁的具体流程 六、静态创建pv和pvc资源由pod运用过程6.1 在NFS主机上创建共享目录&#…

c++11 标准模板(STL)(std::basic_ofstream)(三)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ofstream : public std::basic_ostream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istrea…

【2023 华数杯全国大学生数学建模竞赛】 C 题母亲身心健康对婴儿成长的影响 完整建模方案解析、参考文章及代码

目录 完整思路下载链接&#xff1a;这里获取2023华数杯全国大学生数学建模竞赛题目C 题母亲身心健康对婴儿成长的影响✅ 问题1问题1建模思路✅ 问题2问题2建模思路✅ 问题3问题3建模思路✅ 问题4问题4建模思路✅ 问题5问题5建模思路提供的数据和资料&#xff1a; 完整思路下载链…

【深度学习_TensorFlow】激活函数

写在前面 上篇文章我们了解到感知机使用的阶跃函数和符号函数&#xff0c;它们都是非连续&#xff0c;导数为0的函数&#xff1a; 建议回顾上篇文章&#xff0c;本篇文章将介绍神经网络中的常见激活函数&#xff0c;这些函数都是平滑可导的&#xff0c;适合于梯度下降算法。 写…

iphone卡在恢复模式怎么办?修复办法分享!

iPhone 卡在恢复屏幕问题是 iPhone 用户在软件更新或恢复期间的常见问题。如果你也遇到此问题&#xff0c;不要着急&#xff0c;接下来我们将探讨 iPhone 卡在恢复屏幕上的主要原因&#xff0c;以及如何轻松修复它。 iPhone卡在恢复屏幕问题上没有一个特别的原因&#xff0c;但…

[CKA]考试之检查可用节点数量

由于最新的CKA考试改版&#xff0c;不允许存储书签&#xff0c;本博客致力怎么一步步从官网把答案找到&#xff0c;如何修改把题做对&#xff0c;下面开始我们的 CKA之旅 题目为&#xff1a; Task 检查集群中有多少节点为Ready状态&#xff08;不包括被打上 Taint&#xff1…

games106 homework1实现

games106 homework1 gltf介绍图&#xff1a; 骨骼动画 动画相关属性&#xff1a; 对GLTF的理解参照了这篇文章&#xff1a; glTF格式详解(动画) GLTF文件格式详解 buffer和bufferView对象用于引用动画数据。 buffer对象用来指定原始动画数据, bufferView对象用来引用buff…

《面试1v1》Kafka的ack机制

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

C# 使用堆栈实现队列

232 使用堆栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;、、、&#xff09;&#xff1a;pushpoppeekempty 实现 类&#xff1a;MyQueue void push(int x)将元素 x 推到队列的末尾 int pop()从队列的开头移除并返回元素 in…

基于总线加锁和缓存锁(CPU实现原子操作的两种方式)

总线锁 总线锁就是使用处理器提供的一个 LOCK&#xff03;信号&#xff0c;当一个处理器在总线上输出此信号时&#xff0c;其他处理器的请求将被阻塞住&#xff0c;那么该处理器可以独占共享内存。 CPU和内存之间的通信被锁&#xff01;&#xff01; 如果多个 处 理器同 时对 …

Vue-函数式组件

最近在开发项目的时候&#xff0c;定制了一个公司内部样式的Modal模态框组件。 Modal组件伪代码 <!-- Modal/index.vue--> <template><div class"modal-container" id"modalContainer"><!-- Modal Content --><div class&quo…

linux-MySQL的数据目录

总结&#xff1a; window中的my.ini linux 中 /etc/my.cnfwindow中的D:\soft\mysql-5.7.35-winx64\data linux 中 /var/lib/mysql 1.查找与mysql有关的目录 find / -name mysql [rootVM-4-6-centos etc]# find / -name mysql /opt/mysql /etc/selinux/targeted/tmp/modul…

(4)(4.4) 使用测试版和开发版

文章目录 4.4 使用测试版和开发版 4.4.1 测试版 4.4.2 最新开发版本 4.4.3 自定义固件构建服务器 4.4.4 固件的局限性 4.5 测试 4.4 使用测试版和开发版 4.4.1 测试版 在稳定版(Stable)发布之前&#xff0c;会发布测试版(Beta)。如果你想尝试较新的功能或帮助开发人员飞行…