从源码解析SpringBoot自动配置原理

从源码解析SpringBoot自动配置原理

前言:从”配置地狱”到”开箱即用”

​ 还记得那些年在SSM(Spring + SpringMVC + MyBatis)中挣扎的日子吗?那时候,搭建一个项目的基础环境往往需要半天甚至更长的时间。我们需要配置web.xml,配置Spring容器,配置SpringMVC的DispatcherServlet,配置数据库连接池,配置事务管理器,配置MyBatis的SqlSessionFactory……大量的XML配置充斥在项目中,稍有不慎就会报错,这种状态被称为”配置地狱”(Configuration Hell)。

​ SpringBoot的出现彻底终结了这一切。它提出了”约定优于配置”(Convention Over Configuration)的理念,通过自动配置(Auto Configuration)机制,让我们在几分钟内就能快速搭建起一个独立运行的、生产级别的Spring应用。你只需要引入一个starter依赖,SpringBoot就会自动为你配置好相关的Bean。

​ 那么,这种”开箱即用”的魔法背后究竟隐藏着怎样的秘密?今天,就让我们戴上源码的眼镜,彻底撕开自动配置的神秘面纱。本文我们将基于 SpringBoot 2.7.x 版本进行剖析。

一、从启动类的@SpringBootApplication说起

任何一个SpringBoot应用的起点,都是一个带有main方法的类,而这个类上通常会标注一个无比关键的注解:@SpringBootApplication

1
2
3
4
5
6
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

这个@SpringBootApplication注解聚合了所有自动配置的秘密。点进去看:

1
2
3
4
5
6
7
8
9
10
11
@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 {
// ... 省略了一些属性别名方法
}

可以清晰地看到,@SpringBootApplication是一个复合注解(组合注解)。它的三个核心”左膀右臂”分别是:

  1. **@SpringBootConfiguration**:它实际上就是@Configuration的派生,表示当前类是一个配置类。这意味着你可以直接在启动类中通过@Bean注解向容器注册Bean。
  2. @ComponentScan:启动组件扫描。默认情况下,它会扫描当前启动类所在包及其所有子包下的所有直接带有@Component及其派生注解(如@Service@Repository@Controller)的类,并将其注册为Spring的Bean 。
  3. @EnableAutoConfiguration:这才是自动配置的核心总开关。它的作用就是开启SpringBoot的自动配置功能,让SpringBoot根据类路径下的依赖(jar包)、配置文件和已有Bean的情况,来猜测你需要配置哪些Bean 。

既然@EnableAutoConfiguration是核心,那我们的分析就从它入手。

二、深入@EnableAutoConfiguration

打开@EnableAutoConfiguration的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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 {};
}

这个注解又引入了两个核心组件:

  1. **@AutoConfigurationPackage**:自动配置包,主要用来注册当前启动类所在的包,供后续使用。
  2. **@Import(AutoConfigurationImportSelector.class)**:这是自动配置逻辑真正执行的入口。@Import是Spring框架中用来导入配置类的一个重要注解,而AutoConfigurationImportSelector是一个ImportSelector的实现类,它的selectImports方法会返回一批需要注册到容器中的类的全限定名 。

三、揭秘@AutoConfigurationPackage

先来看第一个小谜题:@AutoConfigurationPackage是做什么的?

源码如下:

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class) // 又见@Import
public @interface AutoConfigurationPackage {
}

它通过@Import导入了AutoConfigurationPackages.Registrar。这是一个ImportBeanDefinitionRegistrar接口的实现类,它的作用是在运行时向容器中注册Bean定义 。

打开Registrar的源码:

1
2
3
4
5
6
7
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 核心:注册一个包含基础包名的Bean
register(registry, new PackageImport(metadata).getPackageName());
}
}

new PackageImport(metadata).getPackageName()这行代码是关键。metadata封装了标注了@AutoConfigurationPackage注解的类的信息。在启动类上,这个类就是MyApplication本身。通过metadata,它获取了MyApplication所在包的包名(例如 com.example.myapp)。

接着调用AutoConfigurationPackages.register方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
// 如果已经存在该Bean,则往Bean的构造函数参数中添加新的包名
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
} else {
// 如果不存在,则创建一个新的Bean,其类型为BasePackages,构造函数参数为包名数组
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}

逻辑小结
@AutoConfigurationPackage的核心作用就是将主启动类所在的包注册为一个名为org.springframework.boot.autoconfigure.AutoConfigurationPackages的Bean。这个Bean内部维护了一个包名的列表。这样,容器中的其他组件如果需要获取”根包”路径(例如用于JPA实体扫描),就可以通过AutoConfigurationPackages.get()方法来获取 。

四、AutoConfigurationImportSelector的源码解剖

现在,让我们聚焦于自动配置的真正大脑——AutoConfigurationImportSelector。它实现了DeferredImportSelector接口,而DeferredImportSelector继承自ImportSelectorDeferredImportSelector的特殊之处在于,它的执行时机是在所有@Configuration配置类都处理完毕之后,这样可以保证自动配置类的优先级低于用户自定义的配置,确保用户覆盖的有效性 。

4.1 入口方法:selectImports

当Spring容器处理@Import(AutoConfigurationImportSelector.class)注解时,会调用其selectImports方法。

1
2
3
4
5
6
7
8
9
10
11
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查自动配置开关是否开启
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 获取自动配置条目
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 3. 将配置类的全限定名转换为String数组返回
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

4.2 开关检查:isEnabled

这一步检查自动配置功能是否被全局禁用。

1
2
3
4
5
6
7
8
protected boolean isEnabled(AnnotationMetadata metadata) {
// 判断当前类是不是AutoConfigurationImportSelector本身
if (getClass() == AutoConfigurationImportSelector.class) {
// 从Environment中读取配置属性 spring.boot.enableautoconfiguration,默认为true
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}

如果你想彻底关闭自动配置,可以在application.properties中设置:spring.boot.enableautoconfiguration=false

4.3 核心逻辑:getAutoConfigurationEntry

这是自动配置最核心的方法,整个流程的精华都浓缩在这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 再次检查开关,虽然前面查过,但这里是protected方法,可能被子类直接调用
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的属性,比如exclude和excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 【重点】1. 获取所有候选的自动配置类(从配置文件中加载)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 2. 去除重复项(因为多个jar包中的spring.factories可能有重复)
configurations = removeDuplicates(configurations);
// 3. 获取需要排除的自动配置类(根据注解属性中的exclude/excludeName)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4. 检查排除类是否有效(排除的类必须是候选类,否则抛出异常)
checkExcludedClasses(configurations, exclusions);
// 5. 从候选列表中移除被排除的类
configurations.removeAll(exclusions);
// 【重点】6. 应用过滤器,根据条件注解(@Conditional)进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 7. 触发自动配置导入事件,用于记录日志和条件评估报告
fireAutoConfigurationImportEvents(configurations, exclusions);
// 8. 封装成AutoConfigurationEntry返回
return new AutoConfigurationEntry(configurations, exclusions);
}

4.3.1 获取候选配置:getCandidateConfigurations

这个方法负责加载所有可能的自动配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过SpringFactoriesLoader加载
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
// 从Spring Boot 2.7开始,增加了一种新的加载方式:从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports加载
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

// 返回的是EnableAutoConfiguration.class,作为SpringFactoriesLoader的key
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

SpringFactoriesLoader.loadFactoryNames方法会扫描classpath下所有jar包中的META-INF/spring.factories文件,找到key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的所有value值(即自动配置类的全限定名),并将其加载进来 。

spring-boot-autoconfigure这个jar包中,就包含了数百个自动配置类的定义。例如:
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

4.3.2 配置过滤:getConfigurationClassFilter().filter()

过滤这一步至关重要。虽然我们加载了上百个自动配置类,但它们并不会全部生效。只有满足了特定条件的才会被实例化,这就是按需装配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ConfigurationClassFilter的内部类
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
// 创建一个候选数组,初始状态为true
boolean[] matches = new boolean[configurations.size()];
int matchCount = 0;
// 遍历所有的过滤器,这里是AutoConfigurationImportFilter的实例
for (AutoConfigurationImportFilter filter : this.filters) {
// 批量匹配,返回布尔数组
boolean[] filterMatches = filter.match(configurations, this.autoConfigurationMetadata);
for (int i = 0; i < filterMatches.length; i++) {
if (!filterMatches[i]) {
matches[i] = false;
}
}
}
// 收集匹配的结果
List<String> result = new ArrayList<>();
for (int i = 0; i < configurations.size(); i++) {
if (matches[i]) {
result.add(configurations.get(i));
}
}
return result;
}

常见的AutoConfigurationImportFilter实现有:

  • **OnClassCondition**:检查类路径下是否存在指定的类(对应@ConditionalOnClass@ConditionalOnMissingClass)。
  • **OnWebApplicationCondition**:检查是否是Web环境(对应@ConditionalOnWebApplication)。
  • **OnBeanCondition**:检查容器中是否存在指定的Bean(对应@ConditionalOnBean@ConditionalOnMissingBean@ConditionalOnSingleCandidate)。

正是通过这一层过滤,自动配置类才能展现出智能的一面 。

五、解析RedisAutoConfiguration

光看理论不够过瘾,我们来剖析一个活生生的自动配置类——RedisAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@AutoConfiguration // Spring Boot 2.7 后新引入的注解,替代了 @Configuration
@ConditionalOnClass(RedisOperations.class) // 关键条件:类路径下必须有 RedisOperations 类
@EnableConfigurationProperties(RedisProperties.class) // 启用属性绑定,将前缀为spring.redis的属性注入
public class RedisAutoConfiguration {

@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 关键条件:容器中不存在名为"redisTemplate"的Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建默认的RedisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean // 关键条件:容器中不存在StringRedisTemplate类型的Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}

5.1 条件注解的魔法

  1. **@ConditionalOnClass(RedisOperations.class)**:
    当你在pom.xml中引入了spring-boot-starter-data-redis依赖后,RedisOperations类就被添加到了classpath中。这个条件通过,RedisAutoConfiguration配置类才可能被加载。如果没有引入相关依赖,这个配置类会被直接跳过 。
  2. @ConditionalOnMissingBean(name = "redisTemplate")
    即使classpath中有Redis的类,RedisAutoConfiguration也不会
    霸蛮
    地创建redisTemplate Bean。它会先看看容器里有没有你自己定义的redisTemplate。如果你已经定义了一个,它就认为你”有自定义需求”,尊重你的选择,不再创建默认的。这正是”允许覆盖”原则的体现 。
  3. **@EnableConfigurationProperties(RedisProperties.class)**:
    这个注解让RedisProperties这个属性类生效。RedisProperties上标注了@ConfigurationProperties(prefix = "spring.redis"),它会将配置文件中所有以spring.redis开头的属性值,如spring.redis.hostspring.redis.port,绑定到这个类的字段上。然后,RedisAutoConfiguration就可以从注入的RedisProperties对象中读取配置,来创建相应的连接工厂或模板 。

六、如何手写一个Starter

理解源码的最终目的是为了更好地应用和扩展。下面我们简单梳理一下如何自定义一个Starter。

6.1 Starter的命名规范

  • 官方Starter:spring-boot-starter-{模块}
  • 自定义Starter:{模块}-spring-boot-starter

6.2 Starter的模块结构

一个完整的Starter通常包含两个模块:

  1. autoconfigure模块:包含自动配置代码和@ConfigurationProperties
  2. starter模块:是一个空jar,仅用于依赖管理,它依赖了autoconfigure模块以及其他必要的依赖。

6.3 关键步骤

  1. 编写属性绑定类:创建XxxProperties类,用于映射配置文件中的属性。

  2. 编写自动配置类:创建XxxAutoConfiguration类,使用@ConditionalOnClass@ConditionalOnMissingBean等注解来控制配置是否生效,并在其中定义@Bean方法创建服务类。同时通过@EnableConfigurationProperties(XxxProperties.class)注入属性。

  3. 配置spring.factories
    resources/META-INF/spring.factories文件中添加:

    properties

    1
    2
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.starter.config.XxxAutoConfiguration

    (Spring Boot 2.7+ 新方式):或者创建resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,直接写入自动配置类的全限定名,一行一个。

七、总结

核心思想:Spring Boot 自动配置通过 @EnableAutoConfiguration 注解开启,利用 AutoConfigurationImportSelector 中的 selectImports 方法,批量加载所有 META-INF/spring.factoriesAutoConfiguration.imports 文件中注册的配置类。然后,再通过一系列条件注解(@Conditional)进行筛选,最终只将满足条件的配置类(及其定义的 Bean)注入到 Spring 容器中 。

一句话总结自动配置 = 批量注册配置类 + 条件装配

常见问题:

  1. @SpringBootApplication 是什么?
    :是一个组合注解,包含 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。相当于把这三个注解的功能聚合在了一起。
  2. @EnableAutoConfiguration 是如何工作的?
    :它通过 @Import(AutoConfigurationImportSelector.class) 导入了一个 ImportSelectorAutoConfigurationImportSelector 会去读取 classpath 下的 META-INF/spring.factoriesAutoConfiguration.imports 文件,获取所有自动配置类的名字,经过排除和条件过滤后,返回给 Spring 容器进行注册。
  3. :Spring Boot 如何做到”按需配置”?
    :通过条件注解,如 @ConditionalOnClass@ConditionalOnMissingBean 等。自动配置类上标注了这些注解,只有当指定的类存在、Bean 不存在等特定条件满足时,该自动配置类才会生效,从而实现了按需加载。
  4. :如何覆盖 Spring Boot 的自动配置?
    :有多种方式,最常用的有两种:1)直接定义自己的 Bean,因为很多自动配置类上都有 @ConditionalOnMissingBean,当检测到你的自定义 Bean 后,它就不会创建默认 Bean。2)在配置文件中通过 exclude 属性排除特定的自动配置类,例如 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  5. :如何在自定义 Starter 中读取配置文件?
    :创建一个 @ConfigurationProperties 注解的属性类来绑定配置,然后在自动配置类上使用 @EnableConfigurationProperties 来引入该属性类,这样就可以在自动配置类中通过依赖注入获取配置值,并在 @Bean 方法中使用。

希望这篇从源码角度的深度解析,能帮你彻底掌握 Spring Boot 的自动配置原理。理解它,不仅能让你在日常开发中更加得心应手,更能在遇到奇怪问题时,拥有深入底层定位问题的能力。


从源码解析SpringBoot自动配置原理
https://johnjoyjzw.github.io/2025/03/18/从源码解析SpringBoot自动配置原理/
Author
JiangZW
Posted on
March 18, 2025
Licensed under