第15篇 · Spring Boot自动配置探秘:约定优于配置的底层逻辑

在前面十多篇里,我们拆解了 IoC 容器、Spring MVC、AOP、事务管理------这些是 Spring 框架的核心能力。但你有没有想过一个问题:在 Spring Boot 出现之前,配置这些东西有多麻烦?

你需要写 web.xml 配置 DispatcherServlet,需要写 applicationContext.xml 配置数据源、事务管理器、视图解析器,需要写 spring-mvc.xml 配置组件扫描......一个项目光 XML 配置文件就有三四个,加起来几百行。

Spring Boot 的出现,把这一切都简化成了几行 application.yml 和一个 @SpringBootApplication 注解。

这一篇,我们把 Spring Boot 自动配置的底层逻辑拆开来看------从 @SpringBootApplication 的组成,到 @EnableAutoConfiguration 的工作原理,再到 spring.factoriesAutoConfiguration.imports 的加载机制。

学习目标

  • 理解 Spring Boot "约定优于配置" 的设计哲学
  • 掌握 @SpringBootApplication 组合注解的三个核心成员
  • 深入理解 @EnableAutoConfiguration工作原理
  • 了解 spring.factories / AutoConfiguration.imports 文件的作用
  • 掌握 @Conditional 条件注解家族的使用
  • 了解如何编写自定义 Starter

正文

一、"约定优于配置"是什么?

"约定优于配置" (Convention over Configuration)是 Spring Boot 最核心的设计理念。它的意思是:框架基于合理的默认约定来工作,开发者只需要在偏离约定时才进行显式配置。

这句话听起来有点抽象,我们用一个具体的例子来说明。

在 Spring MVC 中,视图解析器有一个常见的配置:你需要告诉 Spring,JSP 文件放在哪里、后缀是什么。

xml 复制代码
<!-- 传统的 Spring MVC 配置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

这段配置告诉 Spring:视图文件在 /WEB-INF/views/ 目录下,后缀是 .jsp

Spring Boot 的约定是:如果你用 Thymeleaf,模板文件放在 src/main/resources/templates/ 下,后缀是 .html 。如果你遵循这个约定,就不需要任何配置。

这就是"约定优于配置"------框架有一个默认的约定,你遵循它就能零配置工作。只有当你需要偏离约定时(比如想把模板放在其他目录),才需要显式配置。

Spring Boot 中的典型约定

约定项 Spring Boot 默认值
配置文件位置 src/main/resources/application.yml
静态资源位置 src/main/resources/static/public/
模板文件位置 src/main/resources/templates/
组件扫描路径 启动类所在包及其子包
嵌入式容器端口 8080
数据库连接池 HikariCP

这些约定不是"写死的",而是可覆盖的 ------你可以在 application.yml 中修改它们。但如果你不修改,框架就用默认值帮你工作。

从"XML 地狱"到"零配置"

在 Spring Boot 之前,一个典型的 Spring 项目需要这些配置文件:

  • web.xml:配置 DispatcherServlet、Filter、Listener
  • applicationContext.xml:配置数据源、事务管理器、Service 扫描
  • spring-mvc.xml:配置视图解析器、HandlerMapping、组件扫描
  • mybatis-config.xml:配置 MyBatis 设置

加起来少则几百行,多则上千行。Spring Boot 用自动配置取代了这些 XML------你只需要引入对应的 Starter,框架就会根据类路径中的依赖自动推断并应用合适的配置。

二、@SpringBootApplication 拆解:三个注解的组合

@SpringBootApplication 是 Spring Boot 最核心的注解。我们点开它的源码看看:

java 复制代码
@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 是一个组合注解,它聚合了三个核心注解:

注解 作用
@SpringBootConfiguration 表明这是一个 Spring Boot 配置类(等同于 @Configuration
@EnableAutoConfiguration 启用自动配置------这是 Spring Boot 自动配置的总开关
@ComponentScan 启用组件扫描,默认扫描当前类所在包及其子包

@SpringBootConfiguration :它本质上就是 @Configuration,只是 Spring Boot 单独定义了一个注解来标识"这是一个 Spring Boot 配置类"。

@ComponentScan :默认扫描启动类所在包及其子包下的所有 @Component(以及 @Service@Controller@Repository 等衍生注解)。这意味着你放在启动类所在包外面的组件不会被扫描到------这是新手常踩的坑之一。

@EnableAutoConfiguration :这是自动配置的核心。它告诉 Spring Boot:"根据类路径中的依赖,自动推断并应用合适的配置。"

三、@EnableAutoConfiguration 的奥秘

@EnableAutoConfiguration 是 Spring Boot 自动配置的总开关。我们来看它的源码:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@EnableAutoConfiguration 做了两件关键的事情:

第一件:@AutoConfigurationPackage

这个注解通过 @Import(AutoConfigurationPackages.Registrar.class) 导入了一个 Registrar,它会记录启动类所在的包路径 。这个包路径随后会被用于 @ComponentScan 的默认扫描范围。

第二件:@Import(AutoConfigurationImportSelector.class)

这是自动配置的核心机制AutoConfigurationImportSelector 实现了 ImportSelector 接口,它的作用是:在 Spring 容器启动时,动态加载所有自动配置类

AutoConfigurationImportSelector 的工作流程是这样的:

  1. Spring 在解析 @Import 时,调用 AutoConfigurationImportSelector.selectImports() 方法
  2. 该方法读取配置文件,获取所有自动配置类的全限定名列表
  3. 对这些配置类进行去重排除 处理(根据 excludeexcludeName 属性)
  4. 返回最终需要加载的配置类名称数组
  5. Spring 将这些配置类作为 Bean 定义加载到容器中

关键认知@EnableAutoConfiguration 本身不包含任何配置类------它只是一个"开关"。真正干活的是 AutoConfigurationImportSelector,它负责发现并加载所有的自动配置类。

四、spring.factories 的约定:自动配置类的"清单"

那么,AutoConfigurationImportSelector 是从哪里读取自动配置类的呢?

答案是:META-INF/spring.factories 文件

在 Spring Boot 2.x 及之前的版本中,每个 Starter 的 JAR 包中都包含一个 META-INF/spring.factories 文件。这个文件的内容格式是 key=value ,其中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个 key 对应的 value 就是自动配置类的全限定名列表。

复制代码
# META-INF/spring.factories 示例(Spring Boot 2.x)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

Spring Boot 3.x 的变化

从 Spring Boot 3.0 开始,spring.factories 文件被废弃了 ,取而代之的是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。

新的文件格式更简洁:每行一个配置类的全限定名,不需要 key-value 结构

复制代码
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

为什么要改变?

spring.factories 文件被用于多种目的(自动配置、监听器、初始化器等),所有配置都挤在一个文件里,容易混乱。新的 .imports 文件职责更单一------只用于自动配置类的声明,更加清晰和模块化。

兼容性说明 :如果你在 Spring Boot 3.x 项目中仍然使用 spring.factories 来声明自动配置类,它们不会被加载 。必须迁移到新的 .imports 文件格式。

五、@Conditional 条件配置:为什么自动配置不会"过度配置"

你可能会问:Spring Boot 加载了这么多自动配置类,难道它们全都生效吗?

答案是否定的。如果所有自动配置类都生效,你的应用会启动得非常慢,而且会加载大量不需要的 Bean。

Spring Boot 通过 @Conditional 系列注解 来控制自动配置类是否生效 。每个自动配置类上都标注了一个或多个 @Conditional 注解,只有条件满足时,这个配置类才会被加载。

最常用的条件注解

注解 条件
@ConditionalOnClass classpath 中存在指定的类时才生效
@ConditionalOnMissingClass classpath 中不存在指定的类时才生效
@ConditionalOnBean 容器中存在指定的 Bean 时才生效
@ConditionalOnMissingBean 容器中不存在指定的 Bean 时才生效
@ConditionalOnProperty 配置文件中存在指定的属性且值匹配时才生效
@ConditionalOnWebApplication 当前应用是 Web 应用时才生效

@ConditionalOnMissingBean 的特殊意义

这个注解是 Spring Boot 自动配置可覆盖性 的关键。它的含义是:只有当开发者没有自定义该类型的 Bean 时,自动配置才会创建默认的 Bean

举个例子:DataSourceAutoConfiguration 中会创建一个默认的 DataSource Bean,但它的定义上标注了 @ConditionalOnMissingBean(DataSource.class)。这意味着:

  • 如果你在项目中没有 定义自己的 DataSource,Spring Boot 会根据 application.yml 中的配置自动创建一个
  • 如果你在项目中定义了 自己的 DataSource,Spring Boot 的自动配置就不会覆盖它

这就是 "约定优于配置"的底层实现------框架提供默认值,但你可以随时覆盖。

常见的自动配置加载顺序控制

当多个自动配置类之间有依赖关系时,可以通过 @AutoConfigureBefore@AutoConfigureAfter 来控制加载顺序。例如,DataSourceAutoConfiguration 需要先于 JdbcTemplateAutoConfiguration 加载,因为后者依赖前者创建的数据源。

六、自定义 Starter 实战:把自动配置打包成可复用的模块

理解了自动配置的原理,我们就可以自己动手写一个 Starter 了。

Starter 的组成

一个标准的 Spring Boot Starter 通常包含两个模块:

  • xxx-spring-boot-starter :依赖管理模块,负责引入所需的依赖和 autoconfigure 模块
  • xxx-spring-boot-autoconfigure :自动配置模块,包含配置类和自动配置声明

开发步骤(以 Spring Boot 3.x 为例):

第一步:创建 autoconfigure 模块,编写自动配置类

java 复制代码
package com.example.greeting.autoconfigure;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public GreetingService greetingService(GreetingProperties properties) {
        return new GreetingService(properties.getPrefix(), properties.getSuffix());
    }
}

第二步:定义配置属性类

java 复制代码
package com.example.greeting.autoconfigure;

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

@ConfigurationProperties(prefix = "greeting")
public class GreetingProperties {
    private String prefix = "Hello";
    private String suffix = "!";
    
    // getter / setter 省略
}

第三步:声明自动配置类(Spring Boot 3.x 方式)

src/main/resources/META-INF/spring/ 目录下创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:

复制代码
com.example.greeting.autoconfigure.GreetingAutoConfiguration

第四步:创建 starter 模块(可选)

如果你希望用户只引入一个依赖就能使用,可以创建一个 starter 模块,在它的 pom.xml 中引入 autoconfigure 模块:

xml 复制代码
<dependency>
    <groupId>com.example</groupId>
    <artifactId>greeting-spring-boot-autoconfigure</artifactId>
</dependency>

使用方式

其他项目引入这个 starter 后,只需要在 application.yml 中配置:

yaml 复制代码
greeting:
  prefix: "你好"
  suffix: "~"

然后就可以通过 @Autowired 注入 GreetingService 使用了------完全不需要任何额外的配置

代码示例

示例一:查看 Spring Boot 自动配置报告

Spring Boot 提供了一个非常有用的调试工具------自动配置报告。它告诉你哪些自动配置类生效了,哪些没有,以及没有生效的原因。

方式一:启动时添加 --debug 参数

在启动应用时添加 --debug 参数:

bash 复制代码
java -jar myapp.jar --debug

或者在 IDE 中,在启动配置的 Program arguments 中添加 --debug

方式二:在 application.yml 中配置

yaml 复制代码
debug: true

启动后,控制台会输出一份详细的自动配置报告:

复制代码
============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
      - @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all) did not find any beans (OnBeanCondition)

Negative matches:
-----------------
   JdbcTemplateAutoConfiguration:
      - @ConditionalOnClass required classes 'org.springframework.jdbc.core.JdbcTemplate' found; 
      - @ConditionalOnSingleCandidate (types: javax.sql.DataSource) did not find a primary bean from 'dataSource' (OnBeanCondition)

Positive matches :自动配置生效的类及其原因

Negative matches :自动配置未生效的类及其原因

这份报告是排查自动配置问题的第一手工具。如果你引入了一个 Starter 但它的功能没有生效,先看这份报告------它会告诉你哪个条件不满足。

示例二:自定义一个简单的 Starter

我们来完整实现一个"问候服务"的 Starter。

项目结构

复制代码
greeting-spring-boot-starter/
├── greeting-spring-boot-autoconfigure/
│   ├── src/main/java/com/example/greeting/autoconfigure/
│   │   ├── GreetingService.java
│   │   ├── GreetingProperties.java
│   │   └── GreetingAutoConfiguration.java
│   └── src/main/resources/META-INF/spring/
│       └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── greeting-spring-boot-starter/
    └── pom.xml

1. GreetingService(业务类)

java 复制代码
package com.example.greeting.autoconfigure;

public class GreetingService {
    private final String prefix;
    private final String suffix;

    public GreetingService(String prefix, String suffix) {
        this.prefix = prefix;
        this.suffix = suffix;
    }

    public String greet(String name) {
        return prefix + " " + name + suffix;
    }
}

2. GreetingProperties(配置属性类)

java 复制代码
package com.example.greeting.autoconfigure;

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

@ConfigurationProperties(prefix = "greeting")
public class GreetingProperties {
    private String prefix = "Hello";
    private String suffix = "!";

    public String getPrefix() { return prefix; }
    public void setPrefix(String prefix) { this.prefix = prefix; }
    public String getSuffix() { return suffix; }
    public void setSuffix(String suffix) { this.suffix = suffix; }
}

3. GreetingAutoConfiguration(自动配置类)

java 复制代码
package com.example.greeting.autoconfigure;

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

@Configuration
@ConditionalOnClass(GreetingService.class)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public GreetingService greetingService(GreetingProperties properties) {
        return new GreetingService(properties.getPrefix(), properties.getSuffix());
    }
}

4. 声明自动配置类(Spring Boot 3.x 方式)

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中写入:

复制代码
com.example.greeting.autoconfigure.GreetingAutoConfiguration

5. starter 模块的 pom.xml

xml 复制代码
<dependency>
    <groupId>com.example</groupId>
    <artifactId>greeting-spring-boot-autoconfigure</artifactId>
    <version>1.0.0</version>
</dependency>

使用方

引入 starter 后,在 application.yml 中配置:

yaml 复制代码
greeting:
  prefix: "你好"
  suffix: "~"

在代码中注入使用:

java 复制代码
@RestController
public class HelloController {
    @Autowired
    private GreetingService greetingService;

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

关键观察

  • @ConditionalOnMissingBean 保证了如果使用方自己定义了 GreetingService 的 Bean,自动配置不会覆盖
  • @EnableConfigurationPropertiesapplication.yml 中的 greeting.* 配置自动绑定到 GreetingProperties 对象
  • 使用方零配置即可使用------引入依赖、注入 Bean、直接调用

新手错误 vs 正确姿势

错误表象 根本原因 正确姿势
引入依赖后自动配置未生效,功能没有加载 自动配置的条件不满足(如缺少某个类、某个 Bean 已存在、某个配置属性不匹配) 启动时添加 --debug 查看自动配置报告,找到 Negative matches 中对应的原因
自定义的配置被 Spring Boot 默认配置覆盖了 未使用 @ConditionalOnMissingBean 保护自定义 Bean @Bean 方法上添加 @ConditionalOnMissingBean,让自动配置仅在用户未定义时才生效
Spring Boot 3.x 项目中自定义 Starter 的自动配置不生效 仍然使用 META-INF/spring.factories 声明自动配置类 Spring Boot 3.x 必须使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
@ComponentScan 扫描不到某些包中的 Bean @SpringBootApplication 默认只扫描启动类所在包及其子包 使用 @ComponentScan(basePackages = {"com.example.module1", "com.example.module2"}) 显式指定扫描范围
在 Spring Boot 2.x 和 3.x 之间切换时,自动配置行为不一致 两个版本的自动配置加载机制不同(spring.factories vs .imports 确认项目使用的 Spring Boot 版本,按对应版本的规范配置

疑难深度追问

Q1:spring.factoriesAutoConfiguration.imports 有什么区别?

spring.factories 是 Spring Boot 2.x 及之前版本使用的通用配置文件 ,采用 key-value 格式,一个文件同时用于自动配置、监听器、初始化器等多种 SPI 扩展点。AutoConfiguration.imports 是 Spring Boot 3.x 引入的专用配置文件 ,每行一个配置类全限定名,职责更单一。Spring Boot 3.x 中,spring.factories 中的 EnableAutoConfiguration 条目不再被读取。

Q2:如果多个自动配置类有冲突,Spring Boot 如何决定加载顺序?

通过三个注解控制:

  • @AutoConfigureBefore:指定在某个配置类之前加载
  • @AutoConfigureAfter:指定在某个配置类之后加载
  • @AutoConfigureOrder:指定加载顺序的优先级(数字越小越先加载)

Spring Boot 在加载自动配置类时,会解析这些注解,对配置类进行排序后再依次加载。

Q3:为什么 Spring Boot 的自动配置要使用 @ConditionalOnMissingBean?这个设计有什么好处?

@ConditionalOnMissingBean 实现了 "可覆盖的默认值" 设计模式。它带来的好处:

  • 灵活性:开发者可以完全控制 Bean 的创建逻辑,不被框架的默认实现绑定
  • 渐进式学习:新手可以完全依赖自动配置(开箱即用),高手可以精细覆盖(按需定制)
  • 模块化 :多个 Starter 可以共存,通过 @ConditionalOnMissingBean 避免 Bean 重复定义导致冲突

思考与延伸

  1. 动手验证 :在 Spring Boot 项目中设置 debug: true,启动后查看自动配置报告,找到 3 个 Positive matches 和 3 个 Negative matches,理解它们生效/不生效的原因。

  2. 思考题@ConditionalOnMissingBean@Primary 都能解决"多个同类型 Bean 冲突"的问题,它们的使用场景有什么不同?

  3. 延伸阅读 :Spring Boot 官方文档的 "Developing Auto-configuration" 章节对自定义 Starter 有完整的指南。另外,AutoConfigurationImportSelector 的源码是理解自动配置加载流程的最佳入口。

参考与延伸阅读

  • Spring Boot. Developing Auto-configuration and Using Conditions. Spring Boot Documentation
  • Spring Boot. Auto-configuration. Spring Boot Documentation
  • 腾讯云. Spring Boot自动配置深度解析:@EnableAutoConfiguration与AutoConfigurationImportSelector源码解密. 2025-08-27
  • 阿里云. SpringBoot自动装配机制. 2025-12-11
  • 腾讯云. 什么是约定优于配置?自动配置的原理是什么?. 2025-12-18
  • 阿里云. SpringBoot自动配置的原理是什么?. 2025-07-15
  • 阿里云. SpringBoot中如何自定义starter. 2025-12-12
  • 腾讯云. Spring Boot Starter 自定义开发:封装中间件配置. 2026-02-19