一,什么是 SpringBoot 自动配置?
SpringBoot 自动配置,英文是 Auto-Configuration,它是指基于你引入的依赖 Jar 包,对 SpringBoot 应用进行自动配置。它为 SpringBoot 框架的开箱即用提供了基础支撑。
配置类定义 Configuration Class:
- 广义的配置类:被注解 @Component 直接或间接修饰的某个类,即我们常说的 Spring 组件,其中包括了 @Configuration 类;
- 狭义的配置类:特指被注解 @Configuration 所修饰的某个类,又称为 @Configuration 类;
自动配置不同于自动装配,自动配置是 Auto-Configuration,针对的是 SpringBoot 中的配置类;
而自动装配是 @Autoware,针对是的 Spring 中的依赖注入。
二,Springboot 自动配置实例
实例:Redis 的自动配置
-
引入依赖;
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置 Redis 服务器连接信息;
-
@Autoware 引入 RedisTemplate 对应,即可直接使用;
我们通过 maven 引入一个 starter 外,springboot 就自动完成了 Redis 的配置,将相关的 Bean 对象注册到 IOC 容器中,那么 Springboot 是如何做到这一点的?
三,Springboot 的启动流程(简化版)
自动配置作为Springboot 启动流程的一部分,我们有必要从全局的角度去理解它。启动流程可以理解由5步骤组成,简化版代码:
public static void run(class<?> primaryClass){
//1。创建一个ApplicationContext实例,即我们常说的IoC容器
ApplicationContext context = createApplicationContext()
//2。将主类(primaryClass)注册到IOC器中 (简单但重要的第一步)
loadSourceClass(context,primaryclass);
//3.递归加载并处理所有的配置类
processConfigurationClasses(context);
//4.实例化所有的单例Bean(singleton Bean)
instantiateSingletonBeans(context);
//5。如果是web应用,则启动web 服务器(例如Tomcat)
startWebServer(context);
}
下面主要讲解下第三步,加载并处理所有的配置类。
四,Springboot 加载配置类流程
加载并处理所有的配置类 processConfigurationClasses(context),简化版代码:
public static void processConfigurationCLasses (ApplicationContext context) {
//1。 首先从 IOC 容器中取出当前存在的源配置类
Class<?> sourceConfigurationClass = getSourceConfigurationClass(context);
//2。 创建一个配置类解析器,然后递归加载并处理应用中所有的配置类
ConfigclassParser parser = new ConfigclassParser(context);
parser.parse(sourceConfigurationClass);
//3.1 向IoC需中注顺@Bean方法对应的
BeanDefinitionToadBeanDefinitionsFromBeanMethods(parser.configurationClasses);
//3.2 向IoC谷器中注册ImportBeanDefinitionRegistrar导入的
BeanDefinitionloadBeanDefinitionsFromRegistrars(parser.configurationClasses);
}
首先从 IOC 容器中取出当前存在的源配置类,然后递归处理,处理流程如下:
public void parse(Class<?> configClass){
//1.处理 @ComponentScan: 根据 @ComponentScan扫描得到的 package,得到一系列配置类
if (hasComponentScan(configClass)) {
for (class<?> clazz : doScan(configClass))
this.parse(clazz); //递归处理
}
//2.处理注解 @Import: 根注解 @Import,得到一系列被导入的配置类
if (hasImportedClasses(configClass)) {
for (class<?> clazz : getImports(configClass))
this.parse(clazz); //递归处理
}
//3.处理 @Bean 方法
processBeanMethods(configClass);
//4.处理 Import 入的 ImportBeanDefinitionRegistrar
processRegistrars(configClass);
//5.加入到一个全局的配置类集合中
this.configurationClasses.add(configClass);
}
从源配置类开始,通过注解 @ComponentScan 和 @Import,不断去遍历新的配置类,知道没有新的配置类被发现为止。通过递归遍历,得到一系列配置类后,再对每个配置类进行解析处理,并将得到的 Beandefinition 注册到 IOC 容器中。
可以看到,上述过程中涉及两个重要注解,@ComponentScan 和 @Import。
4.1,SpringBoot 加载配置类的方式
@ComponentScan
@ComponentScan,是来自 Spring 框架的一个注解:
它的作用是对指定的 package 进行扫描,找到符合条件的类,默认是搜索被注解 @Component 修饰的配置类;
通过属性 basePackages 或 basePackageClasses,来指定要进行扫描的 package;
如果未指定 package,则默认扫描当前 @ComponentScan 所修饰的类所在的 package;
@Import
@lmport,是来自 Spring 框架的一个注解,它的作用是提供了一种显式地从其它地方(第三方 Jar 包)加载配置类的方式,这样可以避免使用性能较差的组件扫描(Component Scan)。
支持导入的三种方式:
- 导入普通类,这里的普通是相对于随后的两个接口而言;
- 效果类似于被 @Configuration 所修饰。
- 导入选择器,接口 ImportSelector 的实现类;
- 接口 ImportSelector 中有一个 selectlmports 方法,它的返回值是一个字符串数组,数组中的每个元素分别代表一个将被导入的配置类的全限定名。利用该特性可以给 lOC 容器动态地导入多个配置类。
- 导入注册器,接口 ImportBeanDefinitionRegistrar 的实现类;
- 通过它,我们可以手动将多个 BeanDefinition 注册到 IoC 容器中,从而实现个性化的定制。利用该特性我们可以给 loC 容器动态地导入多个 BeanDefinition。
了解了Springboot 加载配置类的几种方式,那么,自动配置应该使用哪种方式实现呢?
首先排除注解@ComponentScan 。因为使用它很不方便,开发人员需要记住每个第三方Jar 包中的package 名称,然后把它们写到应用程序中;
@lmport 导入普通类方式更不合适,因为甚至要记住第三方 Jar 包中具体的类名;
@lmport 导入注册器方式,它针对的是 BeanDefinition 层面的,其设计目标是对 @Bean 方法的一个补充,并不是用来导入配置类,所以也不合适;
那就剩下了@lmport 导入选择器方式,Springboot 正是通过这种方式,实现自动配置的功能。下面从源码层面验证下这结论。
五,Springboot 自动配置源码剖析
一切都从 @SpringBootApplication 开始:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
接下来看看 @SpringBootApplication 注解源码:
可以看到,它的元注解中,包括 @SpringBootConfiguration 注解,以及 @EnableAutoConfiguration 注解和@ComponentScan 注解。
其中,@EnableAutoConfiguration 的含义是开启了自动配置功能,它使用 @lmport 导入注册器方式导入配置类。
接下来看看 @SpringBootApplication 注解的结构图:
首先,@SpringBootApplication 修饰的类,也会被 @Configuration 间接修饰,即源配置类;
其次,Springboot 框架会对源配置类所在的 package 进行组件扫描(Component Scan);
最重要一点,SpringBoot 框架最终会导入 AutoConfigurationlmportSelector
的一个选择器 来实现自动配置;
问题来了,AutoConfigurationlmportSelector 应该如何实现,才能优雅地去发现 classpath 中 Jar 包的自动配置类 ?
用户只需引入jar 包即可,至于jar包中有哪些自动配置类、类名是什么,等等这些信息用户都不用关心。
5.1,SpringFactories 机制
Spring 框架中有一个 SpringFactories 机制,它是 Java SPI 设计思想的延伸和扩展,它是 Spring 框架的基础机制,在 Spring 以及 SpringBoot 源码中到处可见;可以基于它来实现 SpringBoot 的自动配置功能。
它的核心逻辑是从 classpath 中读取到所有 Jar 包中的配置文件 META-IF/spring.factories,然后根据指定的 key 从配置文件中解析出对应的 value 值。
使用约定的配置文件
一,文件的路径是META-IF/spring.factories,文件内容是 “key=value1,value2,…valueN” 的格式。其中 key 是某个类的全限定名,value 是逗号隔开的多个类名;
全限定类名:就是类名全称,带包路径的用点隔开,例如: java.lang.String
二,第三方 Jar 包,负责提供配置文件,高内聚低耦合,使用 ClassLoader 来读取 classpath 中的配置文件;
三,通过类 SpringFactoriesLoader ,返回一个类名的集合,可以根据实际需求对这些类名进行下一步的处理;
5.2,AutoConfigurationlmportSelector 源码
spring-boot-starter-parent 依赖的版本为 2.2.7.RELEASE
// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//Springboot 自动配置的入口方法
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 1,获取annotationMetadata的注解@EnableAutoConfiguration的属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 2,从资源文件spring.factories中获取EnableAutoConfiguration对应的所有类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
// 3,通过在注解@EnableAutoConfiguration设置exclude属性,排除指定自动配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 4,根据注解@Conditional 来判断是否乤排除某些自动配置类
configurations = this.filter(configurations, autoConfigurationMetadata);
// 5,触发AutoConfiguration 导入的相关事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
我们看看第二步中 getCandidateConfigurations 方法,它是基于SpringFactories 机制来获取第三方 Jar 包中所有自动配置类的方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过 SpringFactories 机制,从配置文件 spring.factories 中找出所有的自动配置类
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;
}
注意到从配置文件获取自动配置类时,使用的 key 是 EnableAutoConfiguration 的全限定名。
总结下 AutoConfigurationlmportSelector 是实现过程:
- 首先基于 SpringFactories 机制加载配置文件,通过 ClassLoader 去获取 classpath 中的配置文件 META-INF/spring.factories;
- 找出所有的自动配置类,在所有的配置文件 METAINF/spring.factories 中,筛选出以EnableAutoConfiguration.class 为 key 的、符合条件的配置类;
- 根据注解 @Conditional 过滤掉不必要的自动配置类;
@Conditional 的作用是只有在特定条件满足时,才会向 IOC 容器注册指定的组件。Spring框架提供了一系列常用的 Conditional 扩展注解:
ConditionalOnBean 注解:当容器中存在指定的 Bean 时,满足条件;
ConditionalOnMissingBean 注解:当容器中不存在指定的 Bean 时,满足条件;
ConditionalOnClass 注解:当classpath 中存在指定的类时,满足条件;
ConditionalOnMissingClass 注解:当classpath 中不存在指定的类时,满足条件;
ConditionalOnProperty 注解:当指定的属性具备指定的值时,满足条件;
ConditionalOnWebApplication 注解:当应用程序是 web 应用时,满足条件;
评论区