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

news2024/11/18 3:44:33

文章目录

  • 前言
  • 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

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

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

相关文章

信必优行业服务能力-中国头部综合性证券公司

近期召开的国家高层会议提出 “要活跃资本市场&#xff0c;提振投资者信心”&#xff0c;明确了下一阶段资本市场发展新任务、新要求&#xff0c;资本市场有望呈现新气象、新风貌。各证券公司积极响应&#xff0c;全力推进资本市场回暖&#xff1b;同时各公司也借此东风修炼内功…

【科普知识】了解电机T型速度曲线和S型速度曲线的区别!

当电机从静止状态启动并加速到额定转速时&#xff0c;其速度变化并非线性的&#xff0c;而是呈现出不同的曲线特征。T型速度曲线和S型速度曲线是两种典型的电机加速曲线类型。那它们之间有什么区别呢&#xff1f;今天&#xff0c;就让我们来深入探讨电机加速曲线的奥秘。 电机速…

国内是不是很缺音视频的开发人员,想学习音视频开发

第一、音视频开发人员的培养是一个长期投入&#xff0c;见效慢的过程&#xff0c;不像有些培训机构&#xff0c;半年培训就可以出去找工作了。同时培训机构最终的目的是快速培训&#xff0c;推荐工作然后挣钱。而音视频开发见效太慢&#xff0c;没有一定时间的锻炼和项目喂养&a…

骨传导耳机什么牌子好?盘点最受欢迎的几款骨传导耳机

骨传导耳机最近一两年越来越受欢迎&#xff0c;市场上不同形态的非入耳式耳机都有&#xff0c;从骨传导&#xff0c;夹耳式到气传导等等都有。骨传导耳机的好处有很多&#xff0c;非入耳式&#xff0c;不伤耳朵&#xff0c;佩戴更舒适更安全。但是一直以来&#xff0c;骨传导耳…

如何把非1024的采样数放入aac编码器

一. aac对数据规格要求 二、代码实现 1.初始化 2.填入数据 3.取数据 三.图解 一. aac对放入的采样数要求 我们知道aac每次接受的字节数是固定的&#xff0c;在之前的文章里有介绍libfdk_aac音频采样数和编码字节数注意 它支持的采样数和编码字节数分别是&#xff1a; fdk_aac …

中规院:2023年黄河流域主要城市人居环境气象评估报告(附下载

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 黄河流域年均高温天数呈上升趋势&#xff0c;近五年达到历史高位。 黄河流域年均高温天数由1978年的11日升至2022年的17日&#xff0c;整体呈上升趋势。1978-2022年间&#xff0c;2018年年均高温天数…

AutoCAD项目外包开发流程

进行AutoCAD的二次开发&#xff0c;一般常用的开发工具是AutoCAD的官方开发工具--ObjectARX。这是一个面向对象的C的开发环境&#xff0c;通过ObjectARX&#xff0c;我们可以直接获取AutoCAD的内核级别的信息&#xff0c;可以大幅度的开发出高效的程序。今天和大家分享这方面的…

【Leetcode刷题】位运算

本篇文章为 LeetCode 位运算模块的刷题笔记&#xff0c;仅供参考。 位运算的常用性质如下&#xff1a; a ^ a 0 a ^ 0 a a ^ 0xFFFFFFFF ~a目录 一. 基本位运算Leetcode29.两数相除Leetcode89.格雷编码 二. 位运算的性质Leetcode136.只出现一次的数字Leetcode137.只出现一…

Python(六十一)字典元素的获取

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

如何在IDEA使用数据库(新手教程)

第一步:将数据库服务器打开 进入你下载好的数据库路径到bin目录下cmd打开命令提示符界面 确认启动数据库 第二步&#xff1a;打开IDEA&#xff0c;找到IDEA数据库 第三步&#xff1a;选择要连接数据库 第四步&#xff1a;根据数据库信息连接数据库 连接之前的页面 输入我们要的…

2022年江西省职业院校技能大赛网络搭建与应用赛项正式赛卷

2022年江西省职业院校技能大赛网络搭建与应用赛项正式赛卷 操作题总分900分&#xff0c;竞赛时长3.5小时 竞赛说明&#xff1a; 1.禁止携带和使用移动存储设备、计算器、通信工具及参考资料。 2.请根据大赛所提供的比赛环境&#xff0c;检查所列的硬件设备、软件及文档清单、…

Java使用POI读取Excel名称管理器

文章目的 本文主要介绍如何使用poi读取到Excel的名称管理器中的内容。并且定位到单元格。 在企业的开发中可能需要通过名称管理器定位到某个单元格&#xff0c;然后在单元格上生成签名。 环境配置 Java&#xff1a;Jdk1.8 poi&#xff1a;5.2.3 maven依赖(pom.xml)&#x…

C语言案例 不重复数字输出--01

题目&#xff1a;有 1、2、3、4 四个数字&#xff0c;能组成多少个互不相同且无重复数字的三位数&#xff1f;都是多少&#xff1f; 步骤一&#xff1a;定义程序目标 编写一个C程序&#xff0c;使用1、2、3、4四个数字组成不相同且不重复的三位数&#xff0c;分别显示出来…

从web漏洞到linux权限提升

从web漏洞到linux权限提升 一、Linux系统介绍与使用二、Linux权限说明2.1、文件权限2.2、linux文件、目录权限说明 三、权限提升 一、Linux系统介绍与使用 linux-全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;是基于POSIXI的多用户、多任务…

关于Monkey稳定性测试,这是我看到最详细的文章

通过随机点击屏幕一段时间&#xff0c;看看app会不会崩溃&#xff0c;能不能维持正常运行&#xff0c;这就是稳定性测试。 01、Monkey是什么 Monkey测试是Android平台自动化测试的一种手段&#xff0c;通过Monkey程序模拟用户触摸屏幕、滑动Trackball、按键等操作来对设备上的…

8.4 作业

1.思维导图 2.判断家目录下&#xff0c;普通文件的个数和目录文件的个数 #!/bin/bash count10 count20 cd ~ for i in $(ls) doif [ -f "$i" ]thencount1$((count11))elif [ -d "$i" ]then count2$((count21))fi done echo $count1 echo $count2 3.输入一…

MySQL数据库免安装版

MySQL数据库免安装 1.安装配置启动 MySQL现在的版本主要分为: 5.x 版本,现在互联网企业中的主流版本,包括:头条、美图、百度、腾讯等互联网公司主流的版本。8.x 版本,新增了一些了窗口函数、持久化配置、隐藏索引等其他功能。所以,我们课程会以常用大版本中最新的版本为…

Camunda BPM Run下载(7.20)

官网地址: https://camunda.com/ 中文站点:http://camunda-cn.shaochenfeng.com https://downloads.camunda.cloud/release/camunda-bpm/run/7.20/https://downloads.camunda.cloud/release/camunda-bpm/run/7.20/camunda-bpm-run-7.20.0-alpha3.ziphttps://downloads.camunda…

科技云报道:震惊!4K、8K画质背后,竟然少不了AI的助力

科技云报道原创。 “对于视频的画质&#xff0c;我现在最低只能够接受720P&#xff0c;最好是1080p。”早五年前&#xff0c;身边就已经有人提出了这样的要求。 随着科技的进步&#xff0c;我们进入了一个视频内容快速增长的时代。从社交媒体到在线教育&#xff0c;从直播购物…

JS解析JSON

在 JavaScript 中解析 JSON 数据 在 JavaScript 中&#xff0c;您可以使用 JSON.parse() 方法来解析 JSON 数据&#xff0c;示例代码如下&#xff1a; var json {"course": {"name": "JavaScript","author": "http://c.bianch…