源码拆解SpringBoot的自动配置机制

SpringBoot相比于Spring系列的前作,很大的一个亮点就是将配置进行了简化,引入了自动化配置,仅靠几个注解和yml文件就取代了之前XML的繁琐配置机制,这也是SpringBoot的独有特点,下面我们从源码角度,一点点拆开自动配置的机制是如何实现的。

从@SpringBootApplication开始

从SpringBoot项目初始类上的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 {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class,attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

发现SpringBootApplication注解是一个入口,里面涵盖了很多关于SpringBoot的启动注解,和自动化配置相关的主要是@EnableAutoConfiguration@ComponentScan

  • @EnableAutoConfiguration:这个注解是Spring Boot自动配置的入口点。它指示Spring Boot启动自动配置过程。
  • @ComponentScan:允许你指定一个或多个包,Spring容器将扫描这些包以及其子包中带有@Component@Service@Repository@Controller等注解的类。

 @EnableAutoConfiguration

打开这个注解的源码,看一下此注解主要做了什么

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

作为主要管控SpringBoot自动配置化的复合注解,抛开常用的,@EnableAutoConfiguration中主要的注解是@AutoConfigurationPackage
@Import其中@AutoConfigurationPackage 用于指示Spring Boot自动配置应该从哪个包开始扫描,是一个Spring扫描类的注解。 @Import则是自动配置的核心逻辑入口,源码中通过@Import导入了一个名为 AutoConfigurationImportSelector的类,而这个类 就是自动配置的加载选择器。

自动配置类的筛选流程

下面看一下AutoConfigurationImportSelector这个选择器主要干了什么:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();private static final String[] NO_IMPORTS = new String[0];private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);//……省略部分成员变量protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;}//……省略部分方法protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}} }

这里AutoConfigurationImportSelector的内容太多,我只摘取了与自动配置相关的逻辑,关于自动化配置加载,主要是getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata)方法,该方法决定哪些自动配置类应该被导入到Spring的Bean定义中。方法的入参为AutoConfigurationMetadataAnnotationMetadata对象,这两个参数提供了自动配置的元数据和注解的元数据,对接外部配置注解与加载机制的连接。后续的逻辑处理需要用到配置信息。

核心流程

首先根据外部参数annotationMetadata来确定整个系统是否开启了自动化加载机制,这里其实是判断@EnableAutoConfiguration注解或spring.boot.enableautoconfiguration属性的设置,在SpringBoot中可以通过这两种方式决定是否启用自动化配置

然后读取@EnableAutoConfiguration注解的控制信息,根据配置信息进行确定加载哪些自动化配置类(携带@Configuration注解的类或者XML文件),排除哪些不需要的配置类(@EnableAutoConfiguration注解指定控制排除一些自动配置);

后面进行容错机制,包含去除重复的类、筛选出要排除的自动化配置类。根据自动配置元数据进一步过滤配置列表;

触发自动配置导入事件,允许其他监听器在自动配置类被导入之前进行操作后创建并返回一个AutoConfigurationEntry对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。 

返回对象的处理

返回的AutoConfigurationEntry 实体对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。这个对象最终会被注册为Spring容器中的一个Bean(Spring中万物皆bean的思想)

AutoConfigurationEntry 是一个内部类,在AutoConfigurationImportSelector类中定义源码如下:

protected static class AutoConfigurationEntry {private final List<String> configurations;private final Set<String> exclusions;private AutoConfigurationEntry() {this.configurations = Collections.emptyList();this.exclusions = Collections.emptySet();}AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {this.configurations = new ArrayList(configurations);this.exclusions = new HashSet(exclusions);}public List<String> getConfigurations() {return this.configurations;}public Set<String> getExclusions() {return this.exclusions;}}

随后Spring的Bean管理会对AutoConfigurationEntry 对象内的候选配置类集合(List<String> configurations)中的类逐一加载,加载中处理每个类上的特殊逻辑, 例如,某个自动配置类的触发条件等等(上篇文章中的LogNoteCondition 的逻辑即是在此处处理的);最后这些自动配置类也会变成bean被加载到Spring的上下文中。被排除的自动配置类(Set<String> exclusions)会被记录,但不会被导入。Spring Boot 会确保这些类在自动配置过程中被忽略。

我们随便启动一个SpringBoot项目,在Debug模式下可以看到处理结束的候选配置类和排除的配置类: 

XXXConfiguration和ConfigurationProperties的注入

debug 里,我们看到了成功装配了AutoConfigurationEntry 对象内的候选配置类,但是这里只是配置类的路径,却还没有配置类的实例对象以及对象内的自动配置值,基于上述的debug,我们选择ServletWebServerFactoryAutoConfiguration进行跟踪

@Configuration(proxyBeanMethods = false
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {//……省略部分代码
}

这里对于ServletWebServerFactoryAutoConfiguration在进行bean加载时需要先处理@EnableConfigurationProperties(ServerProperties.class) ,它的意思是启用指定类的@ConfigurationProperties注解功能;目的是将配置文件中对应的值和 ServerProperties 绑定起来;并把
ServerProperties 加入到 IOC 容器中。

ServerProperties源码:

@ConfigurationProperties(prefix = "server",ignoreUnknownFields = true
)
public class ServerProperties {private Integer port;private InetAddress address;@NestedConfigurationPropertyprivate final ErrorProperties error = new ErrorProperties();private ForwardHeadersStrategy forwardHeadersStrategy;private String serverHeader;private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);private Shutdown shutdown;@NestedConfigurationPropertyprivate Ssl ssl;@NestedConfigurationPropertyprivate final Compression compression;@NestedConfigurationPropertyprivate final Http2 http2;private final Servlet servlet;// ……省略
}

ServerProperties中使用 @ConfigurationProperties注解绑定属性映射文件中的 server 开头的属性。

也就是大部分SpringBoot的yml文件中的:

server:port: 8081servlet:context-path: /testencoding:charset: UTF-8enabled: trueforce: true
spring:#省略后面配置

补充:自定义starter与SpringBoot的合并过程

在 上篇自定义starter中我们对于基本启动类,需要创建META-INFO下的spring.factories文件和主启动类中的@ComponentScan(basePackages = "org.example.lognote.*")注解,这里就连上了。

对于外部使用自定义的starter的SpringBoot项目,它在@EnableAutoConfiguration注解时便会触发自动配置类的筛选流程,通过筛选流程中的getAutoConfigurationEntry方法,根据外部starter项目中的spring.factories配置找到其启动类加载到AutoConfigurationEntry 对象中,随后SpringBean加载机制处理AutoConfigurationEntry 对象中的候选配置类时,starter中的启动类自然作为这个外部SpringBoot项目中的一个简单@Configuration被加载。通过starter项目启动类上的@ComponentScan注解将整个starter中的其他bean都加载到SpringBoot项目中的上下文里。

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

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

相关文章

【自然语言处理】概论(一):自然语言处理概要

1.1 概论&#xff1a;&#xff08;一&#xff09;自然语言处理概要 知识点 自然语言的定义&#xff1a;人类交流使用的&#xff0c;包括口语和书面语的信息交流方式。AI的终极目标&#xff1a;使计算机具备理解&#xff08;听、读&#xff09;和生成&#xff08;说、写&#…

使用 WebSocket 实现实时聊天

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

PySide(PyQt)使用QPropertyAnimation制作动态界面

主脚本&#xff1a; # encoding: utf-8 import os import sysfrom PySide6.QtCore import QPropertyAnimation, QEasingCurvefrom UIS import *# 主画面类 class MainWindow(QMainWindow, animationButton_ui.Ui_MainWindow):def __init__(self):super().__init__()self.setup…

GitHub狂飙3万star的LLM公开资料 - 大模型入门教程

先用一张图片说明这篇blog多火热&#xff01; 本篇大型语言模型&#xff08;LLM&#xff09;课程分为三个部分&#xff1a; &#x1f9e9; LLM基础&#xff1a;涵盖了数学、Python和神经网络的基本知识。 &#x1f9d1;‍&#x1f52c; LLM科学家&#xff1a;专注于使用最新技…

Spring源码学习笔记之@Async源码

文章目录 一、简介二、异步任务Async的使用方法2.1、第一步、配置类上加EnableAsync注解2.2、第二步、自定义线程池2.2.1、方法一、不配置自定义线程池使用默认线程池2.2.2、方法二、使用AsyncConfigurer指定线程池2.2.3、方法三、使用自定义的线程池Excutor2.2.4、方法四、使用…

【代码】Python3|Scrapy框架初探(汽车之家大连市二手车车辆数据爬取、清洗与可视化)

本篇主要是整个项目的介绍&#xff0c;没提到太多琐碎的技术细节&#xff0c;以后有空的话会整理一下 Scrapy 和原生爬虫的差异&#xff0c;还有它坑人的一些地方&#xff0c;单发出来。 开源地址&#xff1a;https://github.com/shandianchengzi/car_home_spider 使用说明&a…

Vue3扁平化Tree组件的前端分页实现

大家好&#xff0c;我是小卷。得益于JuanTree的扁平化设计&#xff0c;在数据量很大的情况下除了懒加载&#xff0c;使用前端分页也是一种解决渲染性能问题的可选方案。 用法 要实现的文档&#xff1a; 分页效果&#xff1a; 实现 新增属性&#xff1a; 组件setup方法中新增…

科普文:万字梳理31个Kafka问题

1、 kafka 是什么,有什么作用 2、Kafka为什么这么快 3、Kafka架构及名词解释 4、Kafka中的AR、ISR、OSR代表什么 5、HW、LEO代表什么 6、ISR收缩性 7、kafka follower如何与leader同步数据 8、Zookeeper 在 Kafka 中的作用&#xff08;早期&#xff09; 9、Kafka如何快…

MobaXterm 软件安装及使用

MobaXterm 软件安装及使用 1. 引言 MobaXterm是一款功能强大的终端软件&#xff0c;支持SSH、Telnet、RDP、VNC、FTP、SFTP、X11转发和串口等远程会话功能。它使得在Windows系统上进行Linux系统的远程管理和文件传输变得简单便捷。 2. MobaXterm 软件下载 下载链接&#xff…

Python数值计算(13)

1. 数学知识 虽然在给定了N个点以后&#xff0c;通过这个点的最小幂多项式是确定的&#xff0c;但是表达方式可不止一种&#xff0c;例如前面提到的系数方式&#xff0c;根方式&#xff0c;还有插值的Lagrange形式等。这里介绍另外一种表达方式&#xff1a; 显然这个式子最高次…

CTF ssrf 基础入门 (一)

0x01 引言 我发现我其实并不是很明白这个东西&#xff0c;有些微妙&#xff0c;而且记忆中也就记得Gopherus这个工具了&#xff0c;所以重新学习了一下&#xff0c;顺便记录一下吧 0x02 辨别 我们拿到一个题目&#xff0c;他的名字可能就是题目类型&#xff0c;但是也有可能…

Java小抄|Java中的List与Map转换

文章目录 1 List<User> 转Map<User.id,User>2 基础类型的转换&#xff1a;List < Long> 转 Map<Long,Long> 1 List 转Map<User.id,User> Map<Long, User> userMap userList.stream().collect(Collectors.toMap(User::getId, v -> v, …

一个优秀的团队里,往往都有这几种人

“独木不成林&#xff0c;单弦难成曲”&#xff0c;一个优秀的团队&#xff0c;需要团队成员之间形成紧密的合作关系&#xff0c;充分发挥各自的优势和特长时&#xff0c;在各自的岗位发光发热&#xff0c;共同推动团队不断向前发展。一个优秀的团队中不可或缺的几个关键角色&a…

视觉SLAM第二讲

SLAM分为定位和建图两个问题。 定位问题 定位问题是通过传感器观测数据直接或间接求解位置和姿态。 通常可以分为两类&#xff1a;基于已知地图的定位和基于未知地图的定位。 基于已知地图的定位 利用预先构建的地图&#xff0c;结合传感器数据进行全局定位。SLAM中的全局…

【计算机网络原理】网络层IP协议的总结和数据链路层以太网协议的总结.

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

Oat++ 后端实现跨域

这里记录在官方的例子中&#xff0c;加入跨域。Oat Example-CRUD 在官方的例子中&#xff0c;加入跨域。 Oat Example-CRUD 修改AppComponent.hpp文件中的代码&#xff0c;如下&#xff1a; #include "AppComponent.hpp"#include "controller/UserController…

服务器数据恢复—raid信息丢失导致RAID无法被识别的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某单位机房搬迁&#xff0c;将所有服务器和存储搬迁到新机房并重新连接线路&#xff0c;启动所有机器发现其中有一台服务器无法识别RAID&#xff0c;提示未做初始化操作。 发生故障的这台服务器安装LINUX操作系统&#xff0c;配置了NF…

【虚拟化】KVM概念和架构

目录 一、什么是KVM&#xff1f; 二、KVM的功能 2.1 主要的功能 2.2 其它功能 三、KVM核心组件及作用 四、KVM与VMware的优势 五、KVM架构 六、qemu介绍 七、创建虚拟机流程 一、什么是KVM&#xff1f; Kernel-based Virtual Machine的简称&#xff0c;KVM 是基于虚拟…

数字看板:跨行业需求下的创新与升级

在当今这个数据驱动的时代&#xff0c;数字看板作为信息展示与决策支持的重要工具&#xff0c;正逐步渗透到各行各业之中。从智慧城市到智能制造&#xff0c;从金融分析到医疗健康&#xff0c;数字看板以其直观、动态、高效的特点&#xff0c;成为了连接数据与决策者的桥梁。本…

代码的魔力:Jupyter Notebook从零开始的探索之旅

1. Jupyter Notebook&#xff1a;探索无限的可能 1.1 Jupyter Notebook的简介 Jupyter Notebook是一个开源的Web应用程序&#xff0c;让你能够创建和共享文档&#xff0c;这些文档可以包含实时代码、数学方程、可视化以及叙述性文本。其名字来源于它支持的三种核心编程语言&am…