SpringBoot 自动装配原理,一文掌握!|原创

news2025/1/4 20:26:46

本文详细讲解了 SpringBoot 自动装配原理,可以直接拉到最后看总结。由于 Spring 源码比较复杂,是需要一些基础的。

如果有不懂的地方,欢迎提问!

点击上方“后端开发技术”,选择“设为星标” ,优质资源及时送达

Spring Boot 一个很大的特点就是极大的简化了原来在 Spring 中复杂的 XML 文件配置过程,让我们对 Spring 应用对搭建和开发变得极其简单。既然可以简化配置,那就意味着很多配置都是需要默认的,这也使其提出了约定大于配置和自动装配的思想。一些通用的配置会默认设置好,整个组件需要的时候直接载入,不需要的时候可以整个卸载。

通过 Spring Boot 我们可以很方便的引入新的组件,只需要在依赖文件中加入对应的 xxx-starter 即可,然后把一些必要的配置比如 url 信息做个简单的设置,或者增加一个 @EnableXXX,就可以开始使用了。

这里以 Feign 为例:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients
public class SampleApplication {

   public static void main(String[] args) {
      SpringApplication.run(SampleApplication.class, args);
   }
}

只需要这样,就可以将 Feign 引入项目中了,接下来根据自己的需要定义对应的 Feign client 即可,是不是非常简单。

Spring Boot 到底是如何做到的呢?

自动装配原理

自动装配的入口

自动装配的基础,是 Spring 从 4.x 版本开始支持 JavaConfig,让开发者可以免去繁琐的 xml 配置形式,而是使用熟悉的 Java 代码加注解,通过 @Configuration、@Bean 等注解可以直接向 Spring 容器注入 Bean 信息。

那么就有种设想,如果我把一些必须的 Bean 以 Java 代码方式准备好呢,只需要引入对应的配置类,相应的 Bean 就会被加载到 Spring 容器中。所以,有了这个基础 Spring Boot 就有了实现自动装配的可能。

还是以 Feign 为例,FeignAutoConfiguration 这个类就是一个 Feign 的自动装配类,我们来探究一下他是如何生效的。

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
      FeignHttpClientProperties.class })
public class FeignAutoConfiguration {

   @Autowired(required = false)
   // 注入了一堆FeignClientSpecification
   private List<FeignClientSpecification> configurations = new ArrayList<>();

   @Bean
   public HasFeatures feignFeature() {
      return HasFeatures.namedFeature("Feign", Feign.class);
   }

   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
   }
}

在每个 Spring Boot 的启动类上,都会有这样一个复合注解 @SpringBootApplication,而它的内部是这样的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 关键注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

……省略其他注解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

上面四个都是一些通用注解,关键在于下面的 @EnableAutoConfiguration注解,从名字就可以看出来,它可以开启自动配置。在之前一篇文章我们已经讲过,通过 @Import 注解我们可以导入一些自定义的 BeanDefination 信息,或者导入一些配置类。在 EnableAutoConfiguration 内部,它使用 @Import 标注了AutoConfigurationImportSelector。

5f4b3f56b2cc2e455429f185259d5619.png

可以看出,它实现了 ImportSelector 接口,之前我们已经在讲结果 @Import 注解是如何生效的。

下面,先对后面涉及的一些类和文件做一个简单介绍。

ImportSelector

看名字就可以知道,这是用于导入的选择器。String[] selectImports() 方法会返回需要导入的配置类的全路径名。在 Spring 容器启动的过程中会调用 invokeBeanFactoryPostProcessors,然后会执行一个重要的后置处理器 ConfigurationClassPostProcessor ,完成配置类的解析,这里会处理 ImportSelector 返回的这些类,将其加载到容器中。

public interface ImportSelector {
   String[] selectImports(AnnotationMetadata importingClassMetadata);
}

DeferredImportSelector

DeferredImportSelector 继承了ImportSelector,它的作用是用于延迟导入。在所有的需要处理的配置类解析过程中,继承此接口的解析排在最后,并且在有多个 DeferredImportSelector 实现类的情况下,可以继承 Ordered 实现排序的效果。

public interface DeferredImportSelector extends ImportSelector {

   @Nullable
   default Class<? extends Group> getImportGroup() {
      return null;
   }

   interface Group {
   }
}

继续之前的内容,AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,所

在执行 ConfigurationClassParser.processImports()方法的时候,最终会调用到下面这段逻辑。一般继承 ImportSelector 会执行其 selectImport 方法。但是这里不同的是,它还继承了 DeferredImportSelector 接口,对 ImportSelector 只是间接继承。在 processImports() 方法中有这样的额外判断,如果是 DeferredImportSelector 的子类,将会执行 deferredImportSelectorHandler.handle(),最终会回调 AutoConfigurationImportSelectorprocess 方法。具体的调用过程请见下图。

17c0ac6571cd6df8fd8a8c83c3151a55.png

Spring Boot 2.x的版本与 1.x有所不同,1.x 是回调 selectImports 方法。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
……
 if (candidate.isAssignable(ImportSelector.class)) {
 ……
    // 执行这段逻辑
 if (selector instanceof DeferredImportSelector) {
  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
 }
 else {
 ……
  processImports(configClass, currentSourceClass, importSourceClasses, false);
 }
 ……
}

读取配置

process() 方法中主要做了一件事,读取并解析 spring.factories 配置文件中的信息,将这些配置文件对应的全路径类名都放入 AutoConfigurationEntry 集合中。接下来详细解释相关逻辑。

getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件。关于这个路径信息,追踪方法可以找到,比较简单。

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

getAutoConfigurationEntry() 方法会获取需要自动装配的类和需要排除的类,读取的文件是 META-INF/spring.factories

关于这个文件路径是怎么指定的,可以在下面方法中看到。一直深入追踪,在 loadSpringFactories 方法中,会加载 META-INF/spring.factories 路径下的配置内容,并且这个路径是硬编码写死的。在全部读取完毕之后,会放在一个 Map 中,key 为类名,value 为对应的自定义配置类。getSpringFactoriesLoaderFactoryClass()  方法会固定返回 EnableAutoConfiguration.class,所以这里只会返回 EnableAutoConfiguration 对应的配置内容,配置文件内容如下图。

4d5e607d1d567be02d2a3035809dcbba.png
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   ……
   // getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件
   // AutoConfigurationEntry 方法会获取需要自动装配的类和需要排除的类
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
   // 添加到 AutoConfigurationEntry集合中等待加载
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

// 返回自动配置的类名,加载Spring.factories中的配置信息
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // loadFactoryNames 会读取对应的配置文件,位置在META-INF/spring.factories中
  // getSpringFactoriesLoaderFactoryClass 返回 EnableAutoConfiguration.class
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
  ……
   return configurations;
}

具体执行方法的调用链路如下:

6345509aea3ac275293aff9a13fcef50.png

加载配置

到这里我们就明白,spring.properties 文件中的配置类是如何加载的,但是问题来了,他什么时候注册到 Spring 容器中呢?

回到之前执行过程中的processGroupImports 方法(在前面的图片已用红框标注了出来),这里会调用 getImports 拿到配置类信息,然后再次调用类信息,然后递归调用 processImports,这个方法之前的文章已经解释过了,如果是配置类会解析并注册 Spring 的 Bean 信息,具体请自行查看之前文章。

引入新模块都在用这个注解,它是如何生效的?|原创

2022-12-11

45818aa916ede6405848461397e1bbea.jpeg
public void processGroupImports() {
   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
     // getImports 得到解析后的类名
      grouping.getImports().forEach(entry -> {
         ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
         try {
           // 再次调用 processImports 递归处理,对配置类解析,注册为 Bean
            processImports(configurationClass, asSourceClass(configurationClass),
                  asSourceClasses(entry.getImportClassName()), false);
         }
         catch (BeanDefinitionStoreException ex) {
           ……
         }
      });
   }
}

另外,再额外说一下 getImports 方法。之前 process 方法并没有返回值,而是把配置信息都保存在了 autoConfigurationEntries 中,所以在执行完 process 之后会紧接着执行 selectImports()。它的功能主要是排除需要排除的类信息,并且在这里按照 spring-autoconfigure-metadata.properties 中指定的顺序排序,然后再返回类信息。

public Iterable<Group.Entry> getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
   // 调用 process 逻辑
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}

public Iterable<Entry> selectImports() {
 if (this.autoConfigurationEntries.isEmpty()) {
  return Collections.emptyList();
 }
 // 获取所有需要排除的类集合
 Set<String> allExclusions = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
 // 获取所有需要装配的类集合
 Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
   .collect(Collectors.toCollection(LinkedHashSet::new));
 // 移除所有排除类
 processedConfigurations.removeAll(allExclusions);
 // 将需要加载的类排序返回,排序规则按照 spring-autoconfigure-metadata.properties 中指定的顺序
 return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
   .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
   .collect(Collectors.toList());
}

总结

最后,我们再次总结一下整个自动装配的过程。

  1. 引入 META-INF/spring.factories 配置文件,在 EnableAutoConfiguration 对应的 value 中配置需要引入的配置类。

  2. 启动类增加 @EnableAutoConfiguration 注解,@SpringBootApplication 已经自带。

  3. @EnableAutoConfiguration 注解中通过 @Import 标注了 AutoConfigurationImportSelector 类。

  4. AutoConfigurationImportSelector 继承了 DeferredImportSelector 接口,在 Spring 生命周期处理 BeanFactoryPostProcessors 的时候会对配置信息进行后置处理,这是会调用到 AutoConfigurationImportSelector.process 方法。

  5. process 方法中会读取 META-INF/spring.factories 配置文件中的内容为 Key-Value 形式,读取完后值返回 key = EnableAutoConfiguration 对应的配置类信息,保存到 autoConfigurationEntries 中。

  6. AutoConfigurationGroup.selectImports 方法返回排序、筛选后的配置类信息,然后依次遍历,递归调用 processImports, 根据这些配置类的全路径名读取并注册在 Spring 容器中。

最后,欢迎大家提问和交流。

如果对你有帮助,欢迎点赞、评论或分享,感谢阅读!

精通MyBatis原理,看这两篇就够了!|原创

2022-12-25

3674ef9dc96ae6de202c4e34e0ea30e0.jpeg

大厂程序员常用的几款「高效工具」,已整理资源!

2022-12-20

bfb3a63046f65df1f9ae364dbfb032c6.jpeg

MySQL主从复制太慢,怎么办?

2022-12-15

e72f750e1248ce81361df1191c3b7063.jpeg

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

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

相关文章

【C语言】函数详解

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️小林爱敲代码       &#x1f6f0;️专栏&#xff1a;✈️C语言快速入门       &#x1f6f0;️欢迎关注&#xff1a;&#x1f44d;点…

【Spring【IOC】】——17、@Resource注解和@Inject注解?

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

【算法题解】6.合并两个有序数组

文章目录题目解法一&#xff1a;双指针解题思路图解代码实现复杂度分析解法二&#xff1a;逆向双指针解题思路图解代码实现复杂度分析题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素…

Plaxis软件:Python命令流自动建模与应用

有限单元法在岩土工程问题中应用非常广泛&#xff0c;很多商业软件如Plaxis/Abaqus/Comsol等都采用有限单元解法。在使用各大软件进行数值模拟建模的过程中&#xff0c;您是否发现GUI界面中重复性的点击输入工作太繁琐&#xff1f;从而拖慢了设计或方案必选进程&#xff1f;针对…

day30【代码随想录】回溯之分割回文串、复原IP地址、子集

文章目录前言一、分割回文串&#xff08;力扣131&#xff09;二、复原IP地址&#xff08;力扣93&#xff09;三、子集&#xff08;力扣78&#xff09;总结前言 1、分割回文串 2、复原IP地址 3、子集 一、分割回文串&#xff08;力扣131&#xff09; 给你一个字符串 s&#xf…

Win10如何显示文件后缀名?显示后缀名的简单方法

现在大多数用户使用的电脑都是Win10系统&#xff0c;有时我们重装电脑系统&#xff0c;会发现电脑出现一些问题&#xff0c;比如文件后缀名不显示出来。如何显示文件后缀名&#xff1f;方法很简单&#xff0c;跟着下面的详细操作步骤走&#xff0c;轻轻松松Get回消失的文件后缀…

Http和Https和SSL工作原理

相关概念 Http&#xff1a;超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上&#xff0c;是Web协议族中非常重要的一个协议。协议版本包含Http1.0、Http1.1 和Http2.0. Https&…

【3D游戏基础】蒙皮骨骼动画与骨架

效果目标&#xff01;画出蒙皮动画的骨架。视频https://www.bilibili.com/video/BV1pM411m7YwPPThttps://zfxdvouj61.feishu.cn/file/boxcnwgESO6zdQetO7oNhKboNsd以下为PPT文字稿&#xff0c;建议还是看视频讲讲自己对蒙皮骨骼动画的理解&#xff0c;并在 Cocos Creator 3.6 中…

全志Tina Linux MPP (多媒体框架)开发指南支持百问网T113 D1-H哪吒DongshanPI-D1s V853-Pro等开发板

1 简述 整理 MPP sample 使用说明文档的目的是&#xff1a;使 MPP sample 更好用。 2 简介 MPP sample 一般存放在 MPP Middleware 的 sample 目录下。此外&#xff0c;MPP Framework 的 demo 目录下也有一些 sample。 本文档主要介绍 MPP Middleware 各 sample 的基本使用方…

【再学Tensorflow2】TensorFlow2的建模流程:疫情发展趋势分析

TensorFlow2的建模流程&#xff1a;疫情发展趋势分析时间序列简介基本概念平稳性白噪声随机游走&#xff08;Random Walk&#xff09;识别一个时间序列Augmented Dickey-Fuller Test(ADF)Kwiatkowski-Phillips-Schmidt-Shin Test(KPSS)时间序列模型预测准确度的衡量衡量预测准确…

舆情监测系统适用哪些行业,如何选择舆情监测系统?

当前&#xff0c;去网上搜索第三方舆情监测工具可以看到很多家不同公司的产品&#xff0c;比如TOOM舆情监测系统&#xff0c;那我们该如何选择舆情监测系统?舆情监测系统到底适用什么行业&#xff0c;带着这些疑问&#xff0c;接下来我们简单了解一下。 ​一、舆情监测系统适…

6.前端笔记-JS-流程控制

1、流程控制 通过控制代码的执行顺序实现我们要完成的功能。控制代码按照什么结构顺序执行 有三种结构&#xff1a;顺序结构、分支结构、循环结构 1.1 顺序流程控制 最简单、最基本的流程控制。程序按照代码的先后顺序&#xff0c;依次执行 1.2 分支流程控制 从上到下执行…

ArcGIS基础实验操作100例--实验8绘制中点连线

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a; 请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验8 绘制中点连线 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

【操作系统】CPU平均负载和使用率

1.CPU的平均负载 &#xff08;1&#xff09;什么是CPU的平均负载 单位时间内系统处于【可运行状态】和【不可中断状态】的平均进程数&#xff0c;就是平均活跃进程数&#xff0c;和CPU使用率并没有直接关系 可运行状态 正在使用CPU或者正在等待CPU的进程用 ps aux命令看到的…

【OpenCV-Python】教程:9-1 级联分类器训练

OpenCV Python 级联分类器训练 【介绍】 使用增强的弱分类器级联包括两个主要阶段: 训练和检测阶段。使用基于HAAR或LBP模型的检测,在object detection tutorial中进行了描述。本文档概述了训练您自己的增强弱分类器级联所需的功能。当前的手册将走过所有不同的阶段: 收集训练…

机器学习-决策树算法原理及实现-附python代码

1.决策树-分类树 sklearn.tree.DecisionTreeClassifier官方地址&#xff1a; https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html#sklearn.tree.DecisionTreeClassifier 在机器学习中&#xff0c;决策树是最常用也是最强大的监督学…

用双因子认证2FA替换Google authenticator谷歌令牌,助力准上市公司实现等保安全审计

21世纪初&#xff0c;某人力资源科技公司试水HR SaaS赛道&#xff0c;以大客户为目标客群&#xff0c;持续深耕&#xff0c;稳扎稳打&#xff0c;如今已是一家专门为中大型企业提供一体化HR SaaS及人才管理产品/解决方案的头部企业。其产品覆盖了从员工招募、入职、管理到离职的…

Linux系统安装Mysql5.7(详解)

Linux系统上安装软件的3种方式&#xff1a; 本次使用二进制发布包安装方式安装Mysql5.7 &#xff08;一&#xff09;下载Mysql5.7的二进制包 这里可以选择去Mysql官网下载&#xff0c;但是由于服务在外国&#xff0c;下载速度实在是太慢了。这里我们可以选择去阿里云的镜像网…

数据通信基础 - 解调技术(PCM)

文章目录1 概述2 脉冲编码调制技术2.1 采样2.2 量化2.3 编码3 扩展3.1 网工软考真题1 概述 #mermaid-svg-K45XtgYRoAw04KU0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-K45XtgYRoAw04KU0 .error-icon{fill:#5522…

医疗影像工具LEADTOOLS 入门教程: 使用文档编写器创建文档 - 控制台 C#

LEADTOOLS是一个综合工具包的集合&#xff0c;用于将识别、文档、医疗、成像和多媒体技术整合到桌面、服务器、平板电脑、网络和移动解决方案中&#xff0c;是一项企业级文档自动化解决方案&#xff0c;有捕捉&#xff0c;OCR&#xff0c;OMR&#xff0c;表单识别和处理&#x…