Spring Boot - 自动配置
# 1. 依赖管理
在我们的pom文件中最核心的依赖就一个:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/>
</parent>
2
3
4
5
6
它的父项目依赖,规定所有依赖的版本信息:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.4</version>
</parent>
2
3
4
5
由此,我们发现springboot框架几乎声明了所有开发中常用的依赖的版本号,无需关注版本号,而且实现了自动版本仲裁机制,当然了我们也可以根据我们的需要,替换掉默认的依赖版本。
# 2. 核心注解@SpringBootApplication
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
2
3
4
5
6
在上面的启动类中我们发现了一个陌生的注解@SpringBootApplication,这个注解的是什么含义呢?我们点进去看一下。
// 声明这是一个Spring Boot的配置类,等价于Spring的@Configuration注解,标明这个类用Spring框架的方式进行配置
@SpringBootConfiguration
@EnableAutoConfiguration // 启用Spring Boot的自动配置机制,尝试根据添加的jar依赖自动配置你的Spring应用
@ComponentScan(
// ComponentScan配置组件扫描,excludeFilters属性用于排除一些组件
excludeFilters = {
// TypeExcludeFilter是Spring Boot定义的一个特殊的Filter,用于排除特定类型的组件
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
// AutoConfigurationExcludeFilter用于排除自动配置的类,这是为了避免自动配置类被重复注册
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
}
)
public @interface SpringBootApplication {
// 应用配置逻辑...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其实@SpringBootApplication是上面三个注解的组合体,我们对这三个注解理解清楚就可以了,下面逐个进行解释:
# 3. @SpringBootConfiguration
- 我们点进去看一下该注解的具体实现
@Target(ElementType.TYPE) // 类或接口级别的注解
@Retention(RetentionPolicy.RUNTIME) // 运行时有效,可通过反射读取
@Documented // 包含在Javadoc中
@Configuration // 标识为Spring配置类,支持@Bean注解定义的Bean
public @interface SpringBootConfiguration {
/**
* 是否代理@Bean方法以强制bean生命周期行为,默认为true。
* 当true时,配置类内部的@Bean方法调用总是返回相同的实例,
* 这有助于保证Spring容器中bean的单例特性。
* 设置为false可以减少运行时的性能开销,但限制了@Bean方法的使用。
* @return 是否代理@Bean方法
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootConfiguration
注解标记一个类为Spring Boot的配置类,这个类可以包含多个@Bean
注解方法来定义和注册bean。当proxyBeanMethods
属性设置为true
(这是默认值),Spring容器将确保在配置类内部通过方法调用引用的bean始终是Spring容器中的同一实例。这种代理机制保证了bean的单例特性,并允许在配置类内部通过简单的Java方法调用来组织bean之间的依赖关系,从而实现了依赖注入和bean管理的灵活性与一致性。
简单来说,Spring Boot的自动配置和@SpringBootConfiguration
注解背后的默认行为就是确保应用中的bean是单例的,并通过CGLIB代理技术保持了配置类中bean方法调用的一致性,让开发者能够以一种简洁且直观的方式管理bean的依赖和配置。
# 4. @EnableAutoConfiguration
@EnableAutoConfiguration
注解是Spring Boot的核心注解之一,用于启用Spring Boot的自动配置机制。它通过@Import(AutoConfigurationImportSelector.class)
导入AutoConfigurationImportSelector
,该选择器负责根据classpath下的内容、定义的条件以及环境属性等因素,选择并应用合适的自动配置类。开发者可以通过exclude
和excludeName
属性来排除某些不希望应用的自动配置类,从而精细控制自动配置的内容。这使得Spring Boot能够提供“开箱即用”的体验,同时也保留了一定程度上的自定义灵活性。
@Target(ElementType.TYPE) // 注解可以被用于类型级别(类、接口等)
@Retention(RetentionPolicy.RUNTIME) // 注解保留至运行时,因此可以通过反射读取
@Documented // 将此注解包含在javadoc中
@Inherited // 允许子类继承父类中的注解
// 自动配置包,将主配置类(通常是@SpringBootApplication标注的类)的包注册为自动配置包
// 这样Spring Boot就能扫描到该包及其子包中的所有组件
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 导入AutoConfigurationImportSelector类,这个类的作用是根据条件选择哪些配置类需要被导入
public @interface EnableAutoConfiguration {
/**
* 用于覆盖自动配置是否启用的环境属性。
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 排除特定的自动配置类,这些类将永远不会被应用。
* @return 要排除的类
*/
Class<?>[] exclude() default {};
/**
* 排除特定的自动配置类名称,这些类将永远不会被应用。
* @return 要排除的类的名称
* @since 1.3.0
*/
String[] excludeName() default {};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. AutoConfigurationPackage指定默认的包规则
@Import(AutoConfigurationPackages.Registrar.class) // 通过@Import注解导入AutoConfigurationPackages的Registrar类
public @interface AutoConfigurationPackage {
// 这个注解没有直接定义方法,其作用主要通过@Import引入的类来实现
}
2
3
4
AutoConfigurationPackage注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理。也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。然后使用@Import注解将其注入到ioc容器中。这样,可以在容器中拿到该路径。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册函数,通过给定的metadata获取到启动类所在的包名,然后注册到Spring容器中
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
// 返回一个由PackageImports对象组成的Set集合,PackageImports包含了启动类所在的包信息
return Collections.singleton(new PackageImports(metadata));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
重点看下registerBeanDefinitions方法中register方法的第二个参数PackageImports方法具体实现
PackageImports(AnnotationMetadata metadata) {
// 从注解元数据中获取@AutoConfigurationPackage的属性
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
// 初始化包名列表,尝试从注解属性中读取basePackages值
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
// 尝试从注解属性中读取basePackageClasses值,将其转换为包名后添加到列表中
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
// 如果上述步骤都没有得到任何包名,则将标注@AutoConfigurationPackage注解的类所在的包作为默认包名
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
// 最终确定的包名列表设置为不可修改状态,用于之后的处理
this.packageNames = Collections.unmodifiableList(packageNames);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
总结:PackageImports
类的作用是解析和确定自动配置包路径。当@AutoConfigurationPackage
注解在某个类上使用时,Spring Boot会通过该类的构造函数来分析注解所在的类或者是通过注解属性指定的包路径。这个过程主要包括以下几个步骤:
- 从注解元数据中获取
@AutoConfigurationPackage
的属性值。 - 尝试从
basePackages
属性中获取包名,如果有指定,则加入到包名列表中。 - 尝试从
basePackageClasses
属性中获取类对象,然后获取这些类所在的包名,加入到包名列表中。 - 如果上述两步都没有得到包名,则使用标注
@AutoConfigurationPackage
注解的类所在的包作为默认包名。 - 确定的包名列表被设置为不可修改,并将用于后续的自动配置包路径的处理。
Registrar
类会使用这些确定的包路径来注册相应的Bean到Spring容器中,这样,Spring Boot的自动配置机制就能在这些包路径下查找并应用可用的配置。这个机制保证了Spring Boot应用的配置能够自动、准确地被识别和注册,极大地简化了配置过程,提高了开发效率。
# 2. @Import(AutoConfigurationImportSelector.class)
当一个配置类被@Import(ImportSelector.class)
注解引用时,Spring容器会实例化这个ImportSelector
,并调用其 process 方法。
这个process
方法是DeferredImportSelector.Group
接口的实现,用于处理DeferredImportSelector
延迟导入选择器。
调用getAutoConfigurationEntry
方法来获取一个AutoConfigurationEntry
实例,其中包含了所有满足条件的自动配置类。
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 断言检查,确保传入的 deferredImportSelector 是 AutoConfigurationImportSelector 的实例
// 只有 AutoConfigurationImportSelector 实例被支持
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 从 deferredImportSelector 获取自动配置入口,这里包含了所有满足条件的自动配置类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
// 将获取到的自动配置入口添加到 autoConfigurationEntries 集合中
// 这个集合存储了所有通过条件检查的自动配置类
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 遍历自动配置入口中的配置类名称
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
// 将配置类名和当前处理的注解元数据关联起来
// 如果 entries 集合中尚不存在该配置类名,则添加之
// 这样做是为了记录哪些自动配置类已被处理
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 检查自动配置是否启用
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取注解属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复的配置类
configurations = removeDuplicates(configurations);
// 获取通过@EnableAutoConfiguration注解排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查排除的类是否在候选配置中,并移除它们
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 应用配置类过滤逻辑(这个过滤器会考虑各种@Conditional注解,只保留那些符合条件的自动配置类。)
configurations = getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回包含最终确定的配置类及排除类的AutoConfigurationEntry对象
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 从META-INF/spring.factories加载自动配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(),
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
spring.factories
spring.factories
文件位于Spring Boot的启动器(starter)和自动配置(autoconfigure)模块的META-INF
目录下。spring.factories
文件采用键值对的形式组织内容,其中一个特别重要的键是org.springframework.boot.autoconfigure.EnableAutoConfiguration
,其值是以逗号分隔的自动配置类的全类名列表。这些自动配置类通常以*AutoConfiguration
结尾,例如DataSourceAutoConfiguration
、JpaAutoConfiguration
等。spring.factories
文件中列出的自动配置类,使得Spring Boot能够智能地提供“开箱即用”的体验,自动配置项目中包含的各种组件(如数据源、JPA、Web服务器等),极大地简化了Spring应用的配置工作。
ImportSelector
接口:这是你在自定义配置时可能会使用的。当你通过@Import
注解引入一个实现了ImportSelector
接口的类时,Spring框架会调用该类的selectImports
方法。这允许开发者基于当前的应用上下文环境动态地向Spring容器中导入一组Bean定义。
process
方法:process
方法并不是ImportSelector
接口中定义的方法。其中DeferredImportSelector
是ImportSelector
的一个扩展,允许其选择的导入操作被延迟到所有@Configuration
类都被处理之后。DeferredImportSelector.Group
的process
方法则是处理这些延迟导入逻辑的一部分。在Spring Boot的自动配置机制中,AutoConfigurationImportSelector
使用了DeferredImportSelector
来确保自动配置类在合适的时机被处理。
# 5. @ComponentScan扩大自动配置扫描范围
@AutoConfigurationPackage
注解和其内部类Registrar
通过@Import
机制被引入,负责自动配置时的包扫描。这里提到的两个属性,basePackages
和basePackageClasses
,是用来自定义扫描包路径的,但这两个属性并不直接存在于@AutoConfigurationPackage
注解中。实际上,在Spring Boot中,自定义包扫描范围通常不是通过直接修改@AutoConfigurationPackage
来实现的,而是使用其他方式,如@ComponentScan
注解。
如果你想扩大Spring Boot的自动配置扫描范围,可以在你的主应用类或任何配置类上使用@ComponentScan
注解,并指定basePackages
或basePackageClasses
属性。通过这种方式,你可以自定义Spring Boot启动时考虑的包范围,不限于应用类所在的包。
例如:
package com.example.myapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
// 扩大扫描范围到com.example包及其子包
@ComponentScan(basePackages = {"com.example"})
public class MyAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
或者使用basePackageClasses
属性指定具体的类,Spring会扫描这些类所在的包及其子包:
@ComponentScan(basePackageClasses = {MyClass1.class, MyClass2.class})
public class MyAppApplication {
// ...
}
2
3
4
通过这种方式,@ComponentScan
注解会覆盖@SpringBootApplication
注解内部默认的包扫描策略,允许你自定义组件扫描的范围。这是调整Spring Boot自动配置扫描范围的推荐方式。
# 6. 解释自动配置如何生效
每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
在Spring Boot的自动配置过程中,@ConditionalOnProperty
注解是用来根据配置文件中的属性值来控制Bean的创建的。如果指定的属性满足特定条件(比如存在特定的值),那么相关的Bean才会被创建和配置。
以ServletWebServerFactoryAutoConfiguration
配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。
在ServletWebServerFactoryAutoConfiguration
类上,有一个@EnableConfigurationProperties
注解:开启配置属性,而它后面的参数是一个ServerProperties
类,这就是习惯优于配置的最终落地点。
在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties
,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties
负责导入这个已经绑定了属性的bean到spring容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties
类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。
至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。
而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。
SpringBoot自动配置生效的工作原理
- 通过定义一个配置属性类(比如
ServerProperties
),使用@ConfigurationProperties
标记,并指定prefix
。 - 在全局配置文件(application.properties或application.yml)中定义与
prefix
相匹配的一组属性。 - 使用
@EnableConfigurationProperties
注解激活配置属性类,使其成为Spring管理的Bean,并从配置文件中读取属性值填充到Bean的属性中。 - 自动配置类(XxxxAutoConfiguration)通过注入XxxxProperties Bean来获取所需的配置属性,并据此创建和配置相应的Bean,导入到Spring容器中。
# 7. 禁用/排除特定的自动配置类
首先回顾一下排除不符合条件的自动配置类的过程主要发生的几个步骤:
1. 获取排除配置:
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
这一步是通过读取@EnableAutoConfiguration
注解上的exclude
和excludeName
属性来获取用户明确指定要排除的自动配置类。
2. 检查并移除排除的配置类:
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
2
这里首先通过checkExcludedClasses
方法确保所有排除的类都是有效的,然后通过configurations.removeAll(exclusions)
将这些排除的类从候选的自动配置类列表中移除。
3. 应用配置类过滤逻辑:
configurations = getConfigurationClassFilter().filter(configurations);
这一步利用ConfigurationClassFilter
进一步过滤掉不符合条件的自动配置类。这个过滤器会考虑各种@Conditional
注解(如@ConditionalOnClass
、@ConditionalOnBean
等),只保留那些符合条件的自动配置类。
通过上述步骤,getAutoConfigurationEntry
方法确保了只有符合条件的自动配置类才会被保留下来,并最终返回一个包含这些配置类的AutoConfigurationEntry
对象。
其中第二步主要提供了一种机制让开发者能够在应用启动时通过@EnableAutoConfiguration
注解的exclude
和excludeName
属性手动排除不需要的自动配置类。这样做可以减少不必要的自动配置加载,优化应用启动时间和运行效率。
例如,如果你的应用中不使用JPA,那么你可能不希望加载与JPA相关的自动配置。你可以在任何一个配置类上使用@EnableAutoConfiguration
注解,并指定排除规则,如下所示:
@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2
3
4
5
6
7
如果使用excludeName
属性来排除自动配置类时,你需要提供自动配置类的完整类名(包括包路径)。
或者在application.properties
或application.yml
中通过配置属性来排除:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
这种方式为开发者提供了灵活的控制,可以根据实际情况选择性地启用或排除某些自动配置,从而使应用更加轻量化和符合特定需求。
# 8. 自动配置总结
- 预加载自动配置类:当Spring Boot应用启动时,它会先加载
META-INF/spring.factories
文件中配置的所有自动配置类(xxxxxAutoConfiguration
)。这些配置类通常是以@Configuration
注解标注的类,它们负责配置和启用Spring应用中的特定功能。 - 条件化配置生效:自动配置类通常会配合各种
@Conditional
注解(如@ConditionalOnClass
、@ConditionalOnBean
、@ConditionalOnProperty
等)来控制配置类或配置类中某些Bean的创建。这些注解确保只有在特定条件满足时,相关的自动配置才会生效。 - 属性绑定:很多自动配置类会使用
@EnableConfigurationProperties
注解来激活特定的配置属性类(xxxxProperties
)。这些配置属性类通过@ConfigurationProperties
注解与配置文件中的属性绑定,从而使得用户可以通过修改application.properties
或application.yml
文件来自定义配置。 - 组件装配:当自动配置类根据条件判断生效后,它会根据配置属性类(
xxxxProperties
)中绑定的属性值来配置和装配Spring容器中的Bean。这一步实际上完成了将功能组件加入到Spring应用上下文的过程。 - 定制化配置:
- 用户可以通过定义自己的
@Bean
方法来覆盖自动配置提供的Bean,实现自定义配置。 - 另外,用户也可以通过修改配置文件中的属性值来自定义已经绑定了
@ConfigurationProperties
的Bean的行为。
- 用户可以通过定义自己的
EnableAutoConfiguration —> 扫描xxxxxAutoConfiguration —> 根据条件@Conditional装配组件 —>根据xxxxProperties加载属性值 ----> application.properties
当面试官问到Spring Boot的自动配置机制时,你可以简洁明了地这样回答:
Spring Boot的自动配置是通过@EnableAutoConfiguration
注解来启动的。这个注解背后的机制会自动扫描并加载META-INF/spring.factories
文件中列出的所有自动配置类。这些配置类通常以AutoConfiguration
作为命名后缀,并且它们实际上是基于Java配置方式实现的Spring容器配置类。
自动配置类可以利用与之配对的Properties类来获取application.properties
或application.yml
中的配置属性。Properties类通过@ConfigurationProperties
注解与配置文件中的属性绑定,该注解使得我们可以轻松地将配置文件中的属性映射到Spring管理的Bean属性上。例如,server.port
属性就可以通过这种方式被读取并设置到相应的Bean中。
这整个机制大大简化了Spring应用的配置工作,开发者只需通过简单的配置就可以控制应用的行为,无需手动编写大量的配置代码。这也体现了Spring Boot的设计哲学:“约定大于配置”。
这样的回答不仅概述了Spring Boot自动配置的工作原理,而且还突出了它如何简化应用配置的特点,既准确又具有针对性。
通过一张图标来理解一下这一繁复的流程:(注意这张图是基于jdk17的,找的配置文件的的名称需要注意不一样)