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 { @Autowired private ApplicationContext context; @Test void 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 { @Override public 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 容器中

Read more

HDFS元数据深度解析:存储位置、持久化机制与一致性保障

HDFS元数据深度解析:存储位置、持久化机制与一致性保障

HDFS元数据深度解析:存储位置、持久化机制与一致性保障 * 引言 * 一、元数据概述:HDFS的"大脑" * 1.1 什么是元数据? * 1.2 元数据的存储形式 * 二、元数据的存储位置 * 2.1 存储路径配置 * 2.2 目录结构解析 * 三、元数据的持久化机制:FsImage与EditLog * 3.1 核心设计思想 * 3.2 工作原理流程图 * 3.3 写入流程详解 * 3.4 检查点机制:合并FsImage和EditLog * 触发条件 * 合并流程 * 3.5 启动恢复流程 * 四、元数据一致性的保障机制 * 4.1 多级一致性保障

By Ne0inhk
极致性能的服务器Redis之Hash类型及相关指令介绍

极致性能的服务器Redis之Hash类型及相关指令介绍

目录 1. Hash介绍 2. hset 3. hget 3. hdel 5. hkeys 6. hvals 编辑 7. hgetall  8. hexists 9. hmget 10. hlen 11. hsetnx 12. hincrby 13. hincrbyfloat 1. Hash介绍 Redis 哈希类型是键值对的集合,字段与值均支持字符串、数字等类型,适合建模用户信息、配置项等对象类数据。其支持单字段 / 多字段的增删改查、字段存在性判断、值自增自减等原子操作,且底层通过压缩列表或哈希表优化存储,空间利用率高、查询效率快,是 Redis 中存储结构化数据的核心类型之一。 在Redis中因为本身就是按照哈希的KV结构来进行存储的,所以当我们想要使用Redis里面的哈希的时候,实际上是哈希的哈希,在后者中,

By Ne0inhk
《算法题讲解指南:优选算法-二分查找》--19.x的平方根,20.搜索插入位置

《算法题讲解指南:优选算法-二分查找》--19.x的平方根,20.搜索插入位置

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 19. x的平方根 题目链接: 题目描述: 题目示例: 解法(二分查找算法): 算法思路: C++算法代码: 算法总结及流程解析: 20. 搜索插入位置 题目链接: 题目描述: 题目示例: 解法(二分查找算法): 算法思路: C++算法代码: 算法总结及流程解析: 结束语 19. x的平方根 题目链接: 69. x 的平方根 - 力扣(LeetCode) 题目描述: 题目示例:

By Ne0inhk

优选算法——前缀和

👇作者其它专栏 《数据结构与算法》《算法》《C++起始之路》 前缀和相关题解 1.前缀和 算法思路: a.先预处理出来一个【前缀和】数组:         用dp[i]表示:[1,i]区间内所有元素的和,那么dp[i-1]里面存的就是[1,i-1]区间内所有元素的和,那么:可得到递推公式:dp[i]=dp[i-1]+arr[i]; b.使用前缀和数组,【快速】求出【某一个区间内】所有元素的和:         当访问的区间是[l,r]时:区间内所有元素的和为:dp[r]-dp[l-r]。 #include <

By Ne0inhk