Spring Boot 自动配置

目录

什么是自动配置?

Spring 加载 Bean

@ComponentScan

@Import

导入类

导入 ImportSelector 接口的实现类

SpringBoot 原理分析

@EnableAutoConfiguration

@Import(AutoConfigurationImportSelector.class) 

AutoConfigurationPackage

SpringBoot 自动配置流程


什么是自动配置?

Spring Boot 的自动配置:当 Spring 容器启动后,一些配置类、bean 对象等就自动存入 Ioc 容器中,而不再需要我们手动去声明,从而简化了程序开发过程,省去了繁琐的配置操作

也就是说,Spring Boot 的自动配置,就是 SpinrgBoot 依赖 jar 包中的配置类以及 Bean 加载到 Spring Ioc 容器中的过程

在本篇文章中,我们主要学习一下两个方面:

1. Spring 如何将对象加载到 Spring Ioc 容器中

2. SpringBoot 是如何进行实现的

我们首先来看 Spring 是如何加载 Bean 的

Spring 加载 Bean

当我们在项目中引入第三方的包时,其实就是在该项目下引入第三方的代码,我们通过在该项目下创建不同的目录来模拟第三方代码的引入:

当前项目目录为 com.example.springautoconfig,模拟第三方代码文件在 com.example.autoconfig 目录下 

第三方文件代码:

@Component
public class AutoConfig {public void test() {System.out.println("test...");}
}

获取 AutoConfig:

@SpringBootTest
class SpringAutoconfigApplicationTests {@Autowiredprivate ApplicationContext context;@Testvoid contextLoads() {AutoConfig bean = context.getBean(AutoConfig.class);System.out.println(bean);}
}

运行结果:

此时显示没有 com.example.autoconfig.AutoConfig 这个类型的 bean

为什么会报错呢?

Spring 使用 类注解(@Controller、@Service、@Repository、@Component、@Configuration) 和 @Bean 注解 帮助我们将 Bean 加载到 Spring Ioc 容器中时,有一个前提:这些注解需要和 SpringBoot 启动类(@SpringBootApplication 标注的类)在同一个目录下

而在上述项目中,启动类所在目录为  com.example.springautoconfig,而 AutoConfig 类位于 com.example.autoconfig 目录下,因此,SpringBoot 并没有扫描到

可是,当我们引入第三方的 jar 包时,第三方的 jar 代码目录也不在启动类的目录下,那么,如何让 Spring 帮我们管理这些 Bean 的呢?

我们可以通过指定路径或引入的文件告诉 Spring,让 Spring 进行扫描

常见的实现方法有两种:

1. @ComponentScan 组件扫描

2. @Import 导入

@ComponentScan

使用 @ComponentScan 注解,指定 Spring 扫描路径:

@SpringBootApplication
@ComponentScan("com.example.autoconfig")
public class SpringAutoconfigApplication {public static void main(String[] args) {SpringApplication.run(SpringAutoconfigApplication.class, args);}
}

运行程序并观察结果:

 成功获取到 AutoConfig bean

那么,Spring Boot 是否使用了这种方式呢?

显然没有。若 Spring Boot 采用这种方式,当我们引入大量的第三方依赖,如 MyBatis、jackson 等时,就需要在启动类上配置不同依赖需要扫描的包,非常繁琐

@Import

@Import 导入主要有以下几种形式:

1. 导入类

2. 导入 ImportSelector 接口的实现类

导入类
@Import(AutoConfig.class)
@SpringBootApplication
public class SpringAutoconfigApplication {public static void main(String[] args) {SpringApplication.run(SpringAutoconfigApplication.class, args);}}

运行程序,观察结果:

成功获取到 AutoConfig Bean

若在文件中有多个配置项:

@Component
public class AutoConfig2 {public void test() {System.out.println("test...");}
}

此时就需要导入多个类:

@Import({AutoConfig.class, AutoConfig2.class})
@SpringBootApplication
public class SpringAutoconfigApplication {public static void main(String[] args) {SpringApplication.run(SpringAutoconfigApplication.class, args);}}

显而易见,这种方式也比较繁琐,因此,Spring Boot 也没有采用

导入 ImportSelector 接口的实现类

实现 ImportSelector 接口:

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 返回需要导入的全限定类名return new String[]{"com.example.autoconfig.AutoConfig", "com.example.autoconfig.AutoConfig2"};}
}

导入:

@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {public static void main(String[] args) {SpringApplication.run(SpringAutoconfigApplication.class, args);}}

运行程序,并观察结果:

这种方式也可以导入第三方依赖提供的 Bean

但是,它们都有一个明显的问题:使用者需要知道第三方依赖中有哪些 Bean 对象或配置类,若我们在导入过程中漏掉了一些 Bean,就可能会导致我们的项目出现问题

依赖中有哪些 Bean,使用时需要配置哪些 Bean,这些问题第三方依赖最为清楚,那么,能否由第三方依赖来做这些事情呢?

比较常见的方法是第三方依赖提供一个注解,而这个注解一般是以 @EnableXxx 开头的注解,而注解中封装的就是 @Import 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// 指定要导入哪些类
@Import(MyImportSelector.class)
public @interface EnableAutoConfig {
}

在启动类上使用第三方提供的注解:

@EnableAutoConfig
@SpringBootApplication
public class SpringAutoconfigApplication {public static void main(String[] args) {SpringApplication.run(SpringAutoconfigApplication.class, args);}}

运行并观察结果:

可以看到,这种方式也可以导入第三方依赖提供的 Bean,且这种方式不需要使用方知道第三方依赖中有哪些 Bean 对象或配置类,而在 Spring Boot 中也是使用的这种方式

SpringBoot 原理分析

SpringBoot 是如何进行实现的?让我们从 SpringBoot 的启动类开始看:

由 @SpringBootApplication 标注的类就是 SpringBoot 项目的启动类:

这个类与普通类的唯一区别就是 @SpringBootApplication 注解,这个注解也是 SpringBoot 实现自动配置的核心

 @SpringBootApplication 是一个组合注解,注解中包含了:

1. 元注解:

 JDK 中提供了 4 个 标准的用来对注解类型进行注解的注解类,称之为 meta-annotation(元注解)

分别为:

@Retention:描述注解保留的时间范围

@Target:描述注解的使用范围

@Documented:描述在使用 javadoc 工具为类生成帮助文档时是否保留其注解信息

@Inherited:使被其修饰的注解具有继承性(若某个类使用了 @Inherited,则其子类将自动具有该注解)

2. @SpringBootConfiguration:

 

标识当前类是一个配置类,里面其实就是 @Configuration,只是做了进一步的封装

其中,@Indexed 注解是用来加速应用启动的

3. @EnableAutoConfiguration(开启自动配置)

是 spiring 自动配置的核心注解,我们在后续详细理解

4.  ComponentScan(包扫描)

可以通过 basePackageClasses basePackages 来定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描,这也是 SpringBoot 项目声明的注解类为什么必须在启动类目录下

也可以自定义过滤器,用于排查一些类、注解等

接下来,我们重点来看  @EnableAutoConfiguration(开启自动配置)

@EnableAutoConfiguration

 @EnableAutoConfiguration 中主要包含两部分:

@Import(AutoConfigurationImportSelector.class) 

@AutoConfigurationPackage

我们先来看  @Import(AutoConfigurationImportSelector.class) 

@Import(AutoConfigurationImportSelector.class) 

使用 @Import 注解,导入了实现 ImportSelector 接口的实现类:

selectImports() 方法中,调用了 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息

我们继续看  getAutoConfigurationEntry() 方法:

在  getAutoConfigurationEntry() 方法中,主要通过 getCandidateConfigurations(annotationMetadata, attributes) 方法 和 fireAutoConfigurationImportEvents(configurations, exclusions) 方法,获取在配置文件中配置的所有自动配置类的集合

我们先看  getCandidateConfigurations(annotationMetadata, attributes) 方法:

在 getCandidateConfigurations(annotationMetadata, attributes) 方法中,使用 ImportCandidates 进行加载

那么,从哪里进行加载呢?

从 断言 的错误信息中我们可以找到答案:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

也就是说, getCandidateConfigurations 方法会获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置类的集合

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:

 其中包含了很多第三方依赖的配置文件 

我们以 redis 为例:

查看 RedisAutoConfiguration

可以看到,在使用 redis 时常用的 RedisTemplate 和 StringRedisTemplate 就位于其中,在我们需要使用时直接使用 @Autowired 进行注入就可以了

但是,由于当前项目并没有引入 redis 相关 jar 包,此时这个类并不能被加载

也就是说在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是会先进行判断,根据 @ConditionalOnMissingBean@ConditionalOnSingleCandidate 等注解的判断进行动态加载

即,在配置文件中使用 @Bean 声明对象,spring 会自动调用配置类中使用 @Bean 标识的方法,并将对象存放到 Spring Ioc 中,但是,在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是会通过 @Conditional 等注解的判断进行动态加载@Conditional 是 spring 底层注解,会根据不同的条件,进行条件判断,若满足指定条件,配置类中的配置才会生效)

我们继续看 fireAutoConfigurationImportEvents 方法,找到是从哪里获取配置类的:

可以看到,fireAutoConfigurationImportEvents 方法最终会从 META-INF/spring.factories 中获取配置类的集合

我们来看 META-INF/spring.factories

META-INF/spring.factories 文件是 Spring 内部提供的一个约定俗称的加载方式,只需要在模块的 META-INF/spring.factories 文件中进行配置,Spring 就会把相应的实现类注入到 Spring 容器中

例如,有一个自动配置类,希望 SpringBoot 在启动时自动加载这个配置,我们就可以在 META-INF/spring.factories 文件按照指定格式进行配置,让 Spring Boot 应用启动时,读取这个 spring.factories 文件,根据文件中指定的配置类来进行自动配置

spring 会加载所有 jar 包下的 META-INF/spring.factories 文件

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports   META-INF/spring.factories 都在引入的起步依赖中:

我们继续看  AutoConfigurationPackage

AutoConfigurationPackage

在这个注解中,主要导入了一个配置文件 AutoConfigurationPackages.Registrar.class

我们继续看 Registrar

 Registrar 实现了 ImportBeanDefinitionRegistrar 接口,可以被 @Import 导入到 spring 容器中

new PackageImports(metadata).getPackageNames().toArray(new String[0]): 当前启动类所在的包名

也就是说,@AutoConfigurationPackage 的作用是 将启动类所在的包下面所有的组件都扫描注册到 spring 容器中

最后,我们来总结一下 SpringBoot 自动配置的流程

SpringBoot 自动配置流程

 SpringBoot 的自动配置的入口是  @SpringBootApplication 注解,在这个注解中,主要封装了 3 个注解:

@SpringBootConfiguration:标识当前类是配置类

@ComponentScan(包扫描):可以通过 basePackageClasses basePackages 定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描,也就是说,默认情况下扫描的是启动类所在的当前包以及子包

@EnableAutoConfiguration(开启自动配置):主要包含两部分:

@Import(AutoConfigurationImportSelector.class) :读取 META-INF/spring.factories 和  META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的配置类​​​​

@AutoConfigurationPackage:将启动类所在的包下所有组件都注入到 Spring 容器中

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

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

相关文章

16.好数python解法——2024年省赛蓝桥杯真题

问题描述 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位…)上的数字是奇数,偶数位(十位、千位、十万位…)上的数字是偶数,我们就称之为“好数”。 给定一个正整数N,请计算从1到N一共有多少个好数。 输入格式 一个整数N。 输出格式 一个整数代表答案。 样例输入 1 …

wxwidgets直接获取系统图标,效果类似QFileIconProvider

目前只做了windows版本&#xff0c;用法类似QFileIconProvider // 头文件 #ifndef WXFILEICONPROVIDER_H #define WXFILEICONPROVIDER_H#include <wx/wx.h> #include <wx/icon.h> #include <wx/image.h> #include <wx/bmpcbox.h> // Include for wxB…

微服务学习-服务调用组件 OpenFeign 实战

1. OpenFeign 接口方法编写规范 1.1. 在编写 OpenFeign 接口方法时&#xff0c;需要遵循以下规范 1.1.1.1. 接口中的方法必须使用 RequestMapping、GetMapping、PostMapping 等注解声明 HTTP 请求的类型。 1.1.1.2. 方法的参数可以使用 RequestParam、RequestHeader、PathVa…

鸿蒙模块概念和应用启动相关类(HAP、HAR、HSP、AbilityStage、UIAbility、WindowStage、window)

目录 鸿蒙模块概念 HAP entry feature har shared 使用场景 HAP、HAR、HSP介绍 HAP、HAR、HSP开发 应用的启动 AbilityStage UIAbility WindowStage Window 拉起应用到显示到前台流程 鸿蒙模块概念 HAP hap包是手机安装的最小单元&#xff0c;1个app包含一个或…

想品客老师的第六天:函数

函数基础的部分写在这里 函数声明 在js里万物皆对象&#xff0c;函数也可以用对象的方式定义 let func new Function("title", "console.log(title)");func(我是参数title); 也可以对函数赋值&#xff1a; let cms function (title) {console.log(tit…

Python:元组构造式和字典推导式

&#xff08;Python 元组构造式和字典推导式整理笔记&#xff09; 1. 元组构造式 1.1 创建元组 使用圆括号&#xff1a; tuple1 (1, 2.5, (three, four), [True, 5], False) print(tuple1) # 输出: (1, 2.5, (three, four), [True, 5], False) 省略圆括号&#xff1a; tup…

Linux之Tcp粘包笔记

目录 一.网络传输四层模型 二.数据传输中数据包传输的两个限制概念 三.数据传输的中粘包问题 四.数据组装的原因 Nagle算法原理: 五.关闭Nagle优化处理粘包问题吗&#xff1f; 六.粘包处理方法 a.设置消息边界&#xff1a; b.定义消息长度&#xff1a; 七.UDP是否会出…

【C语言算法刷题】第2题 图论 dijkastra

题目描述 一个局域网内有很多台电脑&#xff0c;分别标注为 0 ~ N-1 的数字。相连接的电脑距离不一样&#xff0c;所以感染时间不一样&#xff0c;感染时间用 t 表示。 其中网络内一台电脑被病毒感染&#xff0c;求其感染网络内所有的电脑最少需要多长时间。如果最后有电脑不…

软件测试压力太大了怎么办?

本文其实是知乎上针对一个问题的回答&#xff1a; 目前在做软件测试&#xff0c;主要负责的是手机端的项目测试&#xff0c;项目迭代很快&#xff0c;每次上线前验正式都会发现一些之前验测试包时候没有发现的问题&#xff0c;压力太大了&#xff0c;应该怎么调整 看过我之前其…

枚举与模拟 练习

练习题基于《C/C程序设计竞赛真题实战特训教程&#xff08;图解版&#xff09;》 目录 1.1 卡片 题目描述 代码实现 题解笔记 总评 注意点 重点解释 1.2 回文日期 题目描述 输入描述 输出描述 代码实现 题解笔记 总评 注意点 重点解释 1.1 卡片 题目描述 小蓝…

Redis高阶5-布隆过滤器

Redis布隆过滤器 ​ 由一个初始值都为零的bit数组和多个哈希函数构成&#xff0c;用来快速判断集合中是否存在某个元素 目的减少内存占用方式不保存数据信息&#xff0c;只是在内存中做一个是否存在的标记flag 布隆过滤器&#xff08;英语&#xff1a;Bloom Filter&#xff0…

vim如何设置自动缩进

:set autoindent 设置自动缩进 :set noautoindent 取消自动缩进 &#xff08;vim如何使设置自动缩进永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

检测到联想鼠标自动调出运行窗口,鼠标自己作为键盘操作

联想鼠标会自动时不时的调用“运行”窗口 然后鼠标自己作为键盘输入 然后打开这个网页 &#xff08;不是点击了什么鼠标外加按键&#xff0c;这个鼠标除了左右和中间滚轮&#xff0c;没有其他按键了&#xff09;

(Halcon)轮廓等分切割(项目分析)

目标&#xff1a;获取绿色圆所在位置&#xff08;可用于点焊/点胶引导&#xff09; 实现思路 一&#xff0c;相机标定板标定&#xff08;如果实战用于点焊/点胶引导需要做图像畸变校正以减小误差&#xff09; 相机标定 如何做一个C#仿Halcon Calibration插件-CSDN博客 二&…

Java面试题2025-Mysql

1.什么是BufferPool&#xff1f; Buffer Pool基本概念 Buffer Pool&#xff1a;缓冲池&#xff0c;简称BP。其作用是用来缓存表数据与索引数据&#xff0c;减少磁盘IO操作&#xff0c;提升效率。 Buffer Pool由缓存数据页(Page) 和 对缓存数据页进行描述的控制块 组成, 控制…

开始步入达梦中级dba

分析内存使用需要的方法之一 disql /nolog conn sysdba/sysdbaselect value from v$parameter where nameMEMORY_LEAK_CHECK; SP_SET_PARA_VALUE(0,MEMORY_LEAK_CHECK,1); select * from V$MEM_REGINFO; select * from V$MEM_HEAP;

UE求职Demo开发日志#7 强化属性完善

1 实现思路设计 定义一个结构体记录技能树一个单元的信息&#xff0c;命名为FStrengthenCellInfo&#xff0c;一个TArray记录技能树整体信息&#xff0c;需要以下信息&#xff1a; 1.TArray前置技能index 2.FString 描述文本 3.TArray<FMyItemInfo>激活需要的物品ID和…

Qt中QVariant的使用

1.使用QVariant实现不同类型数据的相加 方法&#xff1a;通过type函数返回数值的类型&#xff0c;然后通过setValue来构造一个QVariant类型的返回值。 函数&#xff1a; QVariant mainPage::dataPlus(QVariant a, QVariant b) {QVariant ret;if ((a.type() QVariant::Int) &a…

做Midjourney最好图文教程-提示词公式以及高级参数讲解

先说Midjourney万能公式 填写在绘图提示词框里的内容就是提示词&#xff0c;也叫prompt 用途&#xff1a;让Midjourney生成对应图片&#xff08;符合提示词所描述内容的图片&#xff09;&#xff0c;控制图片生成方向种类&#xff1a;文本提示、图像提示、参数提示&#xff0…

【论文阅读】RAG-Reward: Optimizing RAG with Reward Modeling and RLHF

研究背景 研究问题&#xff1a;这篇文章要解决的问题是如何优化检索增强生成&#xff08;RAG&#xff09;系统&#xff0c;特别是通过奖励建模和人类反馈强化学习&#xff08;RLHF&#xff09;来提高大型语言模型&#xff08;LLMs&#xff09;在RAG任务中的效果。研究难点&…