SpringBoot自动装配原理详解
创始人
2025-05-31 10:40:29

1.SpringBoot自动装配原理详解

要理解SpringBoot自动装配原理肯定离不开@SpringBootApplication注解,如下图所示。

在这里插入图片描述
点进去之后可以发现@SpringBootApplication注解是由许多注解组成,但真正核心的注解只有下面三个:

在这里插入图片描述

1.1 @SpringBootConfiguration 注解

点进@SpringBootConfiguration 注解,可以发现其核心注解为@Configuration注解:

在这里插入图片描述

@Configuration是一个类级别的注释,表明一个对象是 bean 定义的来源。@Configuration类通过带@Bean注解的方法声明 bean 。@Bean@Configuration类方法的调用也可用于定义 bean 间的依赖关系
@Configuration在spring的注解开发中占有很重要的地位,你当你想要定义一个配置类并交给spring管理的时候你就可以在相关类上面加这个注解,并配合@Bean注解把对象交个spring去管理。

所以@SpringBootConfiguration 注解本质上就是一个@Configuration注解,用来标注某个类为 JavaConfig 配置类,有了这个注解就可以在 SpringBoot 启动类中使用```@Bean``标签配置类了,如下图所示。

在这里插入图片描述

1.2 @ComponentScan 注解

@ComponentScan是Spring注解之一,用于在Spring应用程序上下文中启用组件扫描。组件扫描是自动检测和注册 Spring bean(组件)到应用程序上下文中的过程。

这个扫描的范围是:SpringBoot 主启动类的同级路径及子路径,扫描到特定的@Component@Service@Controler@Repository@Configuration等等注解后,会做相应的bean注册和配置文件bean注册工作。

@EnableAutoConfiguration 注解

点进这个注解可以发现 @Import(AutoConfigurationImportSelector.class),如下图所示。

在这里插入图片描述

@Import是Spring注解之一,用于在配置类中导入其他配置类或者普通的Java类。

通过@Impor注解,我们可以将其他配置类或者普通的Java类导入到当前配置类中,从而实现对这些类的引用和使用。可以用于将多个配置类组合在一起,或者引入第三方库中的配置类。

说白了在这里@Impor注解的作用就是将 AutoConfigurationImportSelector 这个类导入当前类,这个类就是实现自动装配的核心。

进入 AutoConfigurationImportSelector 类,找到 selectImports() 方法,见名知意,不难想到这个方法就是选择要加载什么。

在这里插入图片描述
可以发现这个方法的参数是 AnnotationMetadata ,AnnotationMetadata 是 Spring 框架中的一个接口,用于表示一个类或者方法的注解信息。通过 AnnotationMetadata接口,我们可以获取类或方法上的注解信息,并根据注解信息进行相应的操作,如动态创建bean、实现AOP等等。

不难想到这个方法就是根据注解信息加载一些东西,至于加载什么,我们可以看到这个方法的返回值类型是字符串数组,这个字符串数组里面到底要放哪些内容?答案是加载某些配置文件中的全包名列表,这些配置信息包含了Spring Boot自动装配需要的各种配置类,当Spring Boot应用启动时,会根据这些配置信息自动装配相应的组件和配置。至于要加载的这些信息在哪里,我会带大家一点一点的找到。

分析到此处,我们不难想到,要想得到需要的配置信息,必然会有一个方法的参数是注解信息,返回的就是我们需要的配置信息,结合方法源码,果然可以找到这个方法——getAutoConfigurationEntry()。

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

我们发现 autoConfigurationEntry 中保存着我们需要的配置信息,它是通过 getAutoConfigurationEntry 方法获取的,于是我们继续深入,进入 getAutoConfigurationEntry 方法。

在这里插入图片描述
这个方法有很多代码,而且看着长得都差不多,很容易让然眼花缭乱,反正我是这么觉得的,那我们该如何分析呢?既然内容太多,不如先看看结果,看看他想要什么东西。通过方法返回值可以知道我们需要返回一个 AutoConfigurationEntry 对象,再看看最后的 return ,果然是一个新创建的 AutoConfigurationEntry 对象,我们再看看构造这个对象需要的参数:

configurations, exclusions

从名字上来看,显然 configurations 是我们需要的配置信息,而 exclusions 字面意思是排除,也就是不需要的,那我们接下来应该关注configurations 到底是怎么来的。

根据前面的分析可以知道,我们是根据注解类名来从一个配置文件中读取出我们需要的Config配置类,这里configurations就代表了Config配置类,那么我们应该找到一个入口,这个入口跟注解相关,并且返回了 configurations 这个参数。

在这里插入图片描述

目光回到源码,可以发现方法参数是注解的方法有三个,但是返回值是 configurations 的,只有一个:

List configurations = getCandidateConfigurations(annotationMetadata, attributes);

没错,getCandidateConfigurations 方法就是那个我们要找的。在这里我先多说一下:Candidate 候选数据,getCandidateConfigurations 获得候选配置数据,这是什么意思的,在这里我们可以先这样理解,通过这个方法我们获得的配置信息数据是一个全集,我们还要从这里排除一些不需要的,或者是我们自己设置的要排除哪些配置,其实要排除的不就是 exclusions ,不过这里大家留个印象即可,后面我们详细将这里的。

接下来我们继续进入 getCandidateConfigurations 方法:

在这里插入图片描述
走到这里,如果你还没有忘记我们的初衷——“找到要加载的配置信息在哪里”,那我们可以选择走一条捷径,仔细看源码:

在这里插入图片描述
这个断言很巧妙,他告诉我们在哪里没有找到什么东西,要发出警告,我们反其道而行之,去这个路径下看一看。

在这里插入图片描述
妙啊,我们发现了一个类似配置文件的东西(其实就是),我们点开他看一看。

在这里插入图片描述
这个文件里内容填充率爆表啊,至少我刚刚接触代码时,看到这么密密麻麻的东西肯定很难受,所以我们换个角度来看这个文件。

spring.factories文件是Spring框架和Spring Boot中用来配置自动装配的文件之一,它的格式是标准的Java属性文件格式,通常包含以下几个部分:

  • 注释:以#或!开头的注释行,用于描述文件的作用和内容。

  • 配置项:以key=value的形式定义,用于配置自动装配的相关信息。其中,key表示自动装配的配置类或者接口,value表示该配置类或者接口的实现类或者子类

哦,原来如此,不就是个 key-value 结构吗,key表示自动装配的配置类或者接口,value表示该配置类或者接口的实现类或者子类。这个文件包含了我们包含了我们要的配置信息,只要通过 EnableAutoConfiguration 这个注解(key),我们就可以打包带走该配置类或者接口的实现类或者子类这些信息(value)。

到这里,自动装配到底是什么,应该比较清楚了,原来是 SpringBoot 帮我们加载了各种已经写好的Config类文件,实现了这些JavaConfig配置文件的重复利用和组件化。我们不需要再为到底需要哪些类而伤神了,我们只需要通过这个配置文件就可以获得我们需要的类,甚至我们不需要关心怎样获取项目启动所需要的类,只需要一个注解—— @EnableAutoConfiguration。

但是这毕竟是走捷径分析出来的,所以我们还要继续下去,把路走完。但前路漫长,走之前我们要带上一样东西,通过之前的分析,我们不难发现,这个配置文件的 key-value 结构有些奇怪,一个 key 对应着 许多 value,所以我们要通过那种数据结构封装这些信息呢?答案是——Map,大家只需要留个印象即可,接下来我们继续出发。

我们虽然通过断言信息走捷径,知道了配置文件的位置,但是我们还是不知道 SpringBooot 具体是怎样获得这些配置信息的,所以我们要继续分析getCandidateConfigurations 方法,到此时我们显然只有一条路可以走了,那就是进入 loadFactoryNames 方法。
在这里插入图片描述
这个方法中代码并不多,我们只需要关注他的返回值,我们需要继续进入 loadSpringFactories 方法。

在这里插入图片描述
loadSpringFactories 方法内容有点多,为了清晰展示就不截屏了,直接把源码放到这里。

private static Map> loadSpringFactories(ClassLoader classLoader) {Map> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;}

注意看这个方法的第一行代码,Map result = cache.get(classLoader),Map 这个数据结构出现了,能够封装配置文件配置信息的数据结构出现了,这也意味着我们离最终的答案不远了。

在这里进行断点调试:
在这里插入图片描述
果然如此,这个 result 就是我们想要的配置文件中的配置信息,我们之前看的 spring.factories 中关于 @EnableAutoConfiguration注解 的相关信息也被封装到 result 中。

现在我们已经很明确,我们想要的配置信息已经拿到了,所以在这个方法中一定有读取配置文件的地方,那怎么找到它呢?想要读取配置文件,一定要有配置文件的路径,我们继续寻找,果然发现了使用地址信息的地方:

在这里插入图片描述
继续找到这个常量,看,我们发现了什么,“META-INF/spring.factories”,这不正是我们之前走捷径通过断言发现的位置吗?
在这里插入图片描述
继续往下看,有了配置文件的路径,并且通过这个路径拿到了一个,urls 集合,接下来我们要怎么办,当然是遍历 urls ,获取配置信息,然后添加到 result 中进行返回。

看,有源码中显然也是这么做的,通过循环遍历 urls ,然后将结果添加到 result 中。
在这里插入图片描述

我们当然还要继续分析,但是在此之前,我想看看 urls 里面到底是个什么?怎么看呢?当然是断点调试!

在这里插入图片描述
单看 urls 的内容可能比较复杂,但是我们可以单独看一个 url ,这不就是 jar 包对应配置文件的绝对路径吗?有了这个,再去配置文件获取配置信息还不是手到擒来!

其实到这了我们的分析已经可以结束了,我们已经知道 SpringBoot 加载配置信息,通过这些配置信息自动装配需要的类,这个基本流程我们已经走通了,但是我是个有强迫症的人,我已我们继续来看一些细节。

在这里插入图片描述

我对上面两个地方加了断点,因为我比较好奇,他怎么通过 url 加载配置信息的内容,在此之前我们先了解一下 UrlResource类 和 loadProperties 方法。


UrlResource 是 Spring 框架中的一个类,用于表示一个 URL 资源,它的作用主要有以下几个方面:

  1. 封装 URL 资源:UrlResource 将 URL 资源封装为一个对象,方便在程序中处理和操作。

  2. 加载资源:UrlResource 可以加载指定 URL 的资源,例如文件、图片、视频等等,可以通过 getInputStream 方法获取资源输入流,然后进行读取和处理。

  3. 支持多种协议:UrlResource 支持多种 URL 协议,包括 file、http、https、ftp 等等,可以根据需要进行选择。

  4. 用于 Spring 框架中的资源加载:UrlResource 主要用于 Spring 框架中的资源加载,例如加载 Spring 配置文件、加载 bean 定义文件等等。

上面都是一些废话,网上一搜就可找到,总之 UrlResource 就是对 URL 资源进行了封装,方法数据处理和操作。

loadProperties 方法是用来加载 Java 属性文件的方法。它能够将属性文件中的键值对读取到一个 Properties 对象中,方便在程序中使用。


结合上面的知识,我们再看看 resource 对象和 properties 对象,这时我们应该比较清楚了,原来是 loadProperties 方法通过 url 资源将属性文件中的键值对读取到一个 Properties 对象中,我们后面只需要操作 properties 对象内容即可,我们要的配置信息都封装在这个里面了。
在这里插入图片描述

再看看这个循环,不就是从 properties 对象中取出内容,然后经过一些操作,然后添加到 result 中吗。至于做了哪些处理,感兴趣的可以自己看一看,这个不是重点,我就不带着大家一起看了。
在这里插入图片描述
最后还有一个地方需要提一下:

在这里插入图片描述
在这里插入图片描述
上面两张图是 loadSpringFactories 方法的开头和结尾,也没什么太值得强调的,就是 result 是放在缓存里的,需要的时候先去缓存里找,缓存里没有,再通过 loadProperties 方法去加载配置信息,加载完成并添加到 result 后,在方法返回之前,再将 result 存入缓存。

至于为什么这么做,当然是为了减少了磁盘频繁的读写I/O,提高读取速度!

俗话说好马不吃回头草,但是我们毕竟不是马(可能是牛马),我们还是回头看一看 loadFactoryNames 方法,这个方法调用了我们上面分析了好久的 loadSpringFactories 方法,如果你没有忘记 loadSpringFactories 方法返回的应该是那个 result 把,他不是 Map 这种结构吗,而 loadFactoryNames 方法需要的可是 List 这种结构,一个字符串集合。

在这里插入图片描述
所以,不难想到,SpringBoot 必然调用了一个容器类方法,将 Map 数据结构转化成 List 数据结构。妙啊,终于有分析源码的感觉了。

没错这个方法就是 getOrDefault 方法,我在上面的图中已经标注出来了,接下来,进去看看吧。

在这里插入图片描述

好短啊!来,看看他的作用吧。

getOrDefault 是 Java Map 接口中的一个方法,它的作用是获取指定键对应的值,如果该键不存在,则返回一个默认值。

该方法的语法如下:

V getOrDefault(Object key, V defaultValue)

其中,key 表示要获取的键,defaultValue 表示默认值。

当 Map 中存在指定的键时,该方法返回该键对应的值;当 Map 中不存在指定的键时,该方法返回 defaultValue。

原来 SpringBoot 通过 loadSpringFactories 方法获得了 Map 数据结构的 result 再通过 getOrDefault 方法将其转化成 List 数据结构。

最后,我们再回过头来,看一看 getAutoConfigurationEntry 方法。

在这里插入图片描述
可以看到,这里对加载进来的配置配置信息 configurations 进行了去重、排除的操作,这是为了使得用户自定义的排除包生效,同时避免包冲突异常,在SpringBoot的入口函数中我们可以通过注解指定需要排除哪些不用的包,如下图所示。

在这里插入图片描述

至此,整个 SpringBoot 自动装配流程全部分析完成,全文一万多字,纯手敲,如果大家喜欢我的讲解风格,可以给个点赞吗,哈哈。

有任何问题,或者文章有任何错误,请在评论区@我。

相关内容

热门资讯

Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...
unity的C#学习——浮点常... 浮点常量 在C#中,一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。浮点常量...
Angular 开发NPM第三... 准备工作 首先已经安装过node以及angular以及注册过npm账号 新建项目 ng new ...
【Linux Manpage】... NAME libi2c - publicly accessible functions provid...