Spring Boot 自动配置源码拆解:AutoConfigurationImportSelector 到底做了什么

引言

Spring Boot 的"魔法"从哪来?为什么加一个依赖,DataSource 就自动配置好了?为什么 @SpringBootApplication 一个注解就能启动整个应用?

答案藏在 AutoConfigurationImportSelector 这个类里。

本文从 @SpringBootApplication 注解出发,逐层深入源码,拆解自动配置的完整执行链路。读完后,你将彻底理解 Spring Boot 自动配置的工作原理,并能自己写一个 starter。

本文基于 Spring Boot 3.3.x / Spring Framework 6.1.x 源码分析。


一、从 @SpringBootApplication 开始

1.1 注解的"套娃"结构

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

点开 @SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration         // ← 就是 @Configuration
@EnableAutoConfiguration         // ← 自动配置的入口
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {

    // 排除指定的自动配置类
    Class<?>[] exclude() default {};

    // 排除指定的自动配置类名
    String[] excludeName() default {};

    String[] scanBasePackages() default {};
    Class<?>[] scanBasePackageClasses() default {};
    // ...
}

三个核心注解:

注解作用
@SpringBootConfiguration标记配置类(本质是 @Configuration
@EnableAutoConfiguration自动配置入口,本文重点
@ComponentScan包扫描,扫描 @Component 等注解

1.2 @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage       // ← 注册包路径
@Import(AutoConfigurationImportSelector.class)  // ← 关键!导入 Selector
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

关键在 @Import(AutoConfigurationImportSelector.class)——这才是自动配置的真正入口。

@Import 的三种用法:

  1. 导入普通类(直接注册为 Bean)
  2. 导入 @Configuration
  3. 导入 ImportSelectorImportBeanDefinitionRegistrar

AutoConfigurationImportSelector 实现了 ImportSelector 接口,Spring 会调用它的 selectImports() 方法,返回的类名数组全部被注册为配置类。


二、AutoConfigurationImportSelector.selectImports()

2.1 调用链路

SpringApplication.run()
  → refreshContext()
    → invokeBeanFactoryPostProcessors()
      → ConfigurationClassPostProcessor.processConfigBeanDefinitions()
        → ConfigurationClassParser.parse()
          → processImports()  ← 处理 @Import 注解
            → AutoConfigurationImportSelector.selectImports()  ← 入口
              → getAutoConfigurationEntry()
                → getCandidateConfigurations()    ← 加载候选配置
                → removeDuplicates()              ← 去重
                → removeExclusions()              ← 排除
                → filter()                         ← 条件过滤

2.2 selectImports 源码

public class AutoConfigurationImportSelector implements DeferredImportSelector,
        BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 检查是否开启了自动配置(默认开启)
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }

        // 加载自动配置条目(核心方法)
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);

        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected boolean isEnabled(AnnotationMetadata metadata) {
        // 读取 spring.boot.enableautoconfiguration 属性,默认 true
        if (getClass() == AutoConfigurationImportSelector.class) {
            return getEnvironment().getProperty(
                EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
        }
        return true;
    }
}

2.3 getAutoConfigurationEntry 源码

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }

    // 获取 @EnableAutoConfiguration 的属性(exclude, excludeName)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // ① 加载所有候选配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

    // ② 去重
    configurations = removeDuplicates(configurations);

    // ③ 应用排除(@SpringBootApplication.exclude 属性)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);

    // ④ 触发监听器
    fireAutoConfigurationImportEvents(configurations, exclusions);

    return new AutoConfigurationEntry(configurations, exclusions);
}

四个核心步骤:

① 加载候选 → ② 去重 → ③ 排除 → ④ 过滤

三、加载候选配置:getCandidateConfigurations()

3.1 Spring Boot 2.7+ 的变化

Spring Boot 2.7 之前,用 SpringFactoriesLoader 加载 spring.factories

// 老方式(2.7 之前)
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    EnableAutoConfiguration.class, getBeanClassLoader());

读取 META-INF/spring.factories 文件:

# spring-boot-autoconfigure/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
  org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
  # ... 几百个自动配置类

Spring Boot 2.7+,改用 ImportCandidates 加载新文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

// 新方式(2.7+)
List<String> configurations = ImportCandidates.load(
    AutoConfiguration.class, getBeanClassLoader()).getCandidates();

读取新文件(每行一个类名,更简洁):

# spring-boot-autoconfigure/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# ...

3.2 为什么要换

维度spring.factoriesAutoConfiguration.imports
文件格式key=value,一个 key 挂多个 value每行一个类名
文件大小spring.factories 越来越臃肿专用文件,职责单一
加载效率SpringFactoriesLoader 缓存所有 keyImportCandidates 只加载目标
可读性差(长行换行混乱)好(一行一个)

3.3 源码(2.7+ 版本)

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(
            AutoConfiguration.class, getBeanClassLoader()).getCandidates();

    // 兼容老方式(spring.factories 仍然支持,但会告警)
    if (configurations.isEmpty()) {
        configurations = SpringFactoriesLoader.loadFactoryNames(
                EnableAutoConfiguration.class, getBeanClassLoader());
    }

    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring/...AutoConfiguration.imports");

    return configurations;
}

3.4 验证:查看 Spring Boot 自带多少自动配置类

# 解压 spring-boot-autoconfigure jar
jar tf spring-boot-autoconfigure-3.3.2.jar | grep AutoConfiguration.imports

# 查看内容
unzip -p spring-boot-autoconfigure-3.3.2.jar \
  META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | wc -l

# 输出:147(Spring Boot 3.3.2 有 147 个自动配置类)

四、条件过滤:ConfigurationClassFilter

加载到的 147 个候选配置类,不是全部生效的。Spring 会根据条件注解过滤。

4.1 过滤流程

// getAutoConfigurationEntry() 中的过滤调用
configurations = getConfigurationClassFilter().filter(configurations);

// ConfigurationClassFilter.filter()
List<String> filter(List<String> configurations) {
    long startTime = System.currentTimeMillis();

    // 用 AutoConfigurationImportFilter 过滤
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skipped = new boolean[candidates.length];

    // 遍历所有 Filter(OnClassCondition, OnBeanCondition, OnWebApplicationCondition)
    for (AutoConfigurationImportFilter filter : this.filters) {
        boolean[] match = filter.match(candidates, getAutoConfigurationMetadata());
        for (int i = 0; i < candidates.length; i++) {
            if (!match[i]) {
                skipped[i] = true;
            }
        }
    }

    // 收集未被跳过的
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skipped[i]) {
            result.add(candidates[i]);
        }
    }

    return result;
}

4.2 核心条件注解

注解条件示例
@ConditionalOnClassclasspath 存在指定类DataSource 才配置数据源
@ConditionalOnMissingClassclasspath 不存在指定类-
@ConditionalOnBean容器中存在指定 BeanDataSource 才配置 JdbcTemplate
@ConditionalOnMissingBean容器中不存在指定 Bean用户没自定义才用默认的
@ConditionalOnProperty配置属性满足条件spring.datasource.url 存在才配置
@ConditionalOnWebApplication是 Web 应用是 Servlet 应用才配置 MVC
@ConditionalOnNotWebApplication不是 Web 应用-
@ConditionalOnResource资源文件存在classpath:db/migration 存在才配置 Flyway

4.3 源码示例:DataSourceAutoConfiguration

@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
            DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
            DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration {
    }

    // ...
}

解读:

注解含义
@ConditionalOnClass({DataSource.class, ...})classpath 有 JDBC API 才生效
@ConditionalOnMissingBean(type="io.r2dbc...")没用 R2DBC(响应式)才生效
@ConditionalOnMissingBean({DataSource.class, ...})用户没自定义 DataSource 才用默认的

4.4 @ConditionalOnBean 的"坑"

@ConditionalOnBean 有一个经典坑:

@Configuration
public class MyConfig {

    @Bean
    public DataSource myDataSource() {
        return new HikariDataSource();
    }
}

@Configuration
@ConditionalOnBean(DataSource.class)  // ← 可能不生效!
public class JdbcConfig {

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

原因@ConditionalOnBean 在配置类解析阶段判断,此时 MyConfig 可能还没被处理,容器里还没有 DataSource

解决

  1. @AutoConfigureAfter 指定顺序:
@AutoConfigureAfter(MyConfig.class)
@Configuration
@ConditionalOnBean(DataSource.class)
public class JdbcConfig { ... }
  1. 或用 @ConditionalOnClass 代替(判断类而非 Bean)
  2. 官方建议:自定义的自动配置类尽量用 @ConditionalOnClass@ConditionalOnMissingBean,慎用 @ConditionalOnBean

五、排除机制

5.1 三种排除方式

// 方式一:@SpringBootApplication.exclude
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { ... }

// 方式二:@SpringBootApplication.excludeName
@SpringBootApplication(excludeName = {
    "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
})
public class MyApplication { ... }

// 方式三:配置文件
# application.yml
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

5.2 排除源码

protected Set<String> getExclusions(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();

    // ① 从 @EnableAutoConfiguration.exclude 属性获取
    excluded.addAll(asList(attributes, "exclude"));

    // ② 从 @EnableAutoConfiguration.excludeName 属性获取
    excluded.addAll(asList(attributes, "excludeName"));

    // ③ 从配置文件 spring.autoconfigure.exclude 获取
    excluded.addAll(getExcludeConfigurationsProperty());

    return excluded;
}

5.3 排除的典型场景

// 场景:不用 Spring Boot 默认的数据源,用多数据源
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { ... }

// 场景:不用 JPA,用 MyBatis
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class MyApplication { ... }

六、自动配置的执行顺序

6.1 顺序控制注解

注解作用
@AutoConfigureBefore(X.class)在 X 之前配置
@AutoConfigureAfter(X.class)在 X 之后配置
@AutoConfigureOrder(1)数字越小优先级越高

6.2 示例

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnBean(DataSource.class)
public class JdbcTemplateAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(JdbcTemplate.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

JdbcTemplateAutoConfiguration 必须在 DataSourceAutoConfiguration 之后,否则 DataSource 还没创建。

6.3 排序源码

// AutoConfigurationSorter
public List<String> getByInDependencyOrder(Collection<String> classNames) {
    // 1. 构建依赖图
    Map<String, AutoConfigurationClass> classes = new HashMap<>();
    for (String className : classNames) {
        classes.put(className, new AutoConfigurationClass(className, ...));
    }

    // 2. 拓扑排序
    List<String> ordered = new ArrayList<>();
    Set<String> visited = new HashSet<>();

    for (String className : classNames) {
        visit(className, classes, visited, ordered);
    }

    return ordered;
}

七、Debug:看自动配置报告

7.1 开启 debug 模式

# application.yml
debug: true

或启动参数:

java -jar myapp.jar --debug

7.2 ConditionEvaluationReport

启动日志会输出自动配置报告:

============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:(生效的自动配置)
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
      - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory; SearchStrategy: all) did not find any beans (OnBeanCondition)

   DataSourceConfiguration.Hikari matched:
      - @ConditionalOnClass found required class 'com.zaxxer.hikari.HikariDataSource' (OnClassCondition)
      - @ConditionalOnProperty (spring.datasource.type=com.zaxxer.hikari.HikariDataSource) matched (OnPropertyCondition)

Negative matches:(未生效的自动配置)
-----------------
   DataSourceConfiguration.Tomcat did NOT match:
      - @ConditionalOnClass did not find required class 'org.apache.tomcat.jdbc.pool.DataSource' (OnClassCondition)

   RedisAutoConfiguration did NOT match:
      - @ConditionalOnClass did not find required class 'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)

Unconditional classes:(无条件限制,始终生效)
----------------------
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

Exclusions:(被排除的)
----------
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

7.3 用 Actuator 查看生效的配置

management:
  endpoints:
    web:
      exposure:
        include: conditions

访问 http://localhost:8080/actuator/conditions,返回 JSON 格式的报告。


八、实战:手写一个 Starter

8.1 需求

写一个 hello-spring-boot-starter,引入后自动配置一个 HelloService,支持配置自定义问候语。

8.2 项目结构

hello-spring-boot-starter/
├── pom.xml
└── src/main/
    ├── java/com/example/hello/
    │   ├── HelloAutoConfiguration.java       # 自动配置类
    │   ├── HelloProperties.java              # 配置属性绑定
    │   └── HelloService.java                 # 核心服务
    └── resources/
        └── META-INF/
            ├── spring/
            │   └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
            └── additional-spring-configuration-metadata.json

8.3 核心代码

HelloProperties.java

package com.example.hello;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "hello")
public class HelloProperties {

    private String prefix = "Hello";     // 问候前缀
    private String suffix = "!";         // 问候后缀
    private boolean enabled = true;      // 是否启用

    // getter/setter 省略
}

HelloService.java

package com.example.hello;

public class HelloService {

    private final HelloProperties properties;

    public HelloService(HelloProperties properties) {
        this.properties = properties;
    }

    public String sayHello(String name) {
        if (!properties.isEnabled()) {
            return "Hello feature is disabled";
        }
        return properties.getPrefix() + ", " + name + properties.getSuffix();
    }
}

HelloAutoConfiguration.java

package com.example.hello;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(
    prefix = "hello",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true        // 不配置时默认启用
)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean    // 用户没自定义才用默认的
    public HelloService helloService(HelloProperties properties) {
        return new HelloService(properties);
    }
}

注册自动配置类(Spring Boot 2.7+ 方式):

# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.hello.HelloAutoConfiguration

8.4 使用

在业务项目中引入依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

配置:

hello:
  prefix: 你好
  suffix: ~
  enabled: true

使用:

@RestController
public class DemoController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        return helloService.sayHello(name);  // 输出:你好, 张三~
    }
}

8.5 自定义覆盖

用户可以自定义 HelloService 覆盖默认的:

@Configuration
public class MyConfig {

    @Bean
    public HelloService helloService(HelloProperties properties) {
        // 自定义实现,默认的不会创建(因为 @ConditionalOnMissingBean)
        return new MyCustomHelloService(properties);
    }
}

这就是 @ConditionalOnMissingBean 的核心价值:默认配置可被用户覆盖


九、总结

自动配置完整流程

@SpringBootApplication
  → @EnableAutoConfiguration
    → @Import(AutoConfigurationImportSelector.class)
      → selectImports()
        → getAutoConfigurationEntry()
          ├── ① getCandidateConfigurations()  加载 147 个候选
          │     └── ImportCandidates.load() 读取 .imports 文件
          ├── ② removeDuplicates()           去重
          ├── ③ getExclusions() + removeAll() 排除
          │     ├── @SpringBootApplication.exclude
          │     ├── @SpringBootApplication.excludeName
          │     └── spring.autoconfigure.exclude 配置项
          └── ④ ConfigurationClassFilter.filter() 条件过滤
                ├── @ConditionalOnClass     类是否存在
                ├── @ConditionalOnBean      Bean 是否存在
                ├── @ConditionalOnProperty  属性是否满足
                └── @ConditionalOnWebApplication 是否 Web 应用
          ↓
        最终生效的配置类(通常 20-30 个)
          ↓
        解析为 BeanDefinition 注册到容器
          ↓
        实例化 Bean

核心设计思想

设计作用
约定优于配置加依赖即生效,不用手动配置
条件化配置根据环境自动适配,按需生效
可覆盖@ConditionalOnMissingBean 让用户能覆盖默认
可排除exclude 机制让用户能禁用不想要的
可排序@AutoConfigureAfter 保证依赖顺序

面试常考

问题答案要点
Spring Boot 自动配置原理?@Import(Selector)selectImports() → 加载 .imports → 条件过滤
spring.factories 和 .imports 的区别?2.7+ 改用 .imports 文件,格式更简洁,职责单一
@ConditionalOnBean 的坑?解析阶段判断,可能 Bean 还没创建,用 @AutoConfigureAfter
如何禁用某个自动配置?exclude 属性 或 spring.autoconfigure.exclude 配置
如何查看生效了哪些自动配置?--debug 启动 或 /actuator/conditions

互动话题:你写过自定义 Starter 吗?遇到最大的坑是什么?欢迎留言讨论!


参考资料


标题:Spring Boot 自动配置源码拆解:AutoConfigurationImportSelector 到底做了什么
作者:jiangyi
地址:http://jiangyi.space/articles/2026/08/04/1785576729637.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消