SpringBoot 自动配置原理详解——从“约定优于配置“到源码全程追踪

定位 :SpringBoot 自动配置是面试高频考点,也是理解 SpringBoot "开箱即用"能力的关键。很多人只知道加个 @SpringBootApplication 就能跑起来,但一问"它是怎么做到的?"就卡壳了。本文从生活比喻 出发,结合源码逐层剥析,带你真正理解自动配置的完整原理。

目录

  • [1. 自动配置的由来------为了解决什么问题?](#1. 自动配置的由来——为了解决什么问题?)
  • [2. 什么是自动配置?](#2. 什么是自动配置?)
  • [3. 从一个问题开始:为什么 SpringBoot 不需要写 XML?](#3. 从一个问题开始:为什么 SpringBoot 不需要写 XML?)
  • [4. @SpringBootApplication------一切的起点](#4. @SpringBootApplication——一切的起点)
  • [5. @EnableAutoConfiguration------自动配置的核心开关](#5. @EnableAutoConfiguration——自动配置的核心开关)
  • [6. spring.factories / AutoConfiguration.imports------配置清单从哪来](#6. spring.factories / AutoConfiguration.imports——配置清单从哪来)
  • [7. 条件注解------精准控制配置是否生效](#7. 条件注解——精准控制配置是否生效)
  • [8. 源码追踪:自动配置的完整执行流程](#8. 源码追踪:自动配置的完整执行流程)
  • [9. 实战:手写一个自定义 Starter](#9. 实战:手写一个自定义 Starter)
  • [10. 自动配置的覆盖与排除](#10. 自动配置的覆盖与排除)
  • [11. 调试技巧:如何查看哪些自动配置生效了](#11. 调试技巧:如何查看哪些自动配置生效了)
  • [12. 面试题精选与回答思路](#12. 面试题精选与回答思路)
  • [13. 总结](#13. 总结)

1. 自动配置的由来------为了解决什么问题?

在讲自动配置的原理之前,我们先回到原点:SpringBoot 的自动配置机制到底是为了解决什么问题才诞生的?

Spring 框架的成功与痛点

Spring 框架自 2003 年诞生以来,凭借 IoC(控制反转)和 AOP(面向切面编程)两大核心能力,迅速成为 Java 企业级开发的事实标准。但随着 Spring 生态越来越庞大,一个严重的问题逐渐暴露出来:

配置地狱(Configuration Hell)。

复制代码
Spring 项目的典型痛点:
═══════════════════════════════════════

痛点1:XML 配置爆炸
- 一个中等规模的项目,XML 配置文件动辄几百上千行
- web.xml、applicationContext.xml、spring-mvc.xml、spring-security.xml...
- 新人接手项目,光看配置文件就要花半天

痛点2:重复劳动
- 每个新项目都要配 DispatcherServlet
- 每个新项目都要配 DataSource
- 每个新项目都要配 TransactionManager
- 这些配置 90% 都是一样的,但每次都要从头写

痛点3:依赖管理混乱
- Spring MVC 要引哪些 jar?版本号是多少?
- Spring + Hibernate 要引哪些 jar?会不会冲突?
- 一个版本升级可能导致一串依赖要跟着改

痛点4:环境搭建耗时
- 新建一个能跑的 Spring Web 项目,至少要 30 分钟
- 大部分时间花在配置上,而不是写业务代码
- 对新手极不友好,劝退率很高

一个真实的对比

假设你要搭一个最简单的 Spring Web 项目连接 MySQL 数据库:

复制代码
传统 Spring 的搭建步骤:
═══════════════════════════════════════

第1步:创建 Maven 项目,手动引入十几个依赖
- spring-core、spring-context、spring-web、spring-webmvc
- spring-jdbc、spring-tx
- mysql-connector-java
- jackson-databind(JSON 处理)
- servlet-api
- ...还要确保版本兼容

第2步:配置 web.xml
- 配置 DispatcherServlet
- 配置 ContextLoaderListener
- 配置字符编码过滤器

第3步:配置 spring-mvc.xml
- 配置 ViewResolver
- 配置 HandlerMapping
- 配置消息转换器
- 开启注解驱动

第4步:配置 applicationContext.xml
- 配置 DataSource
- 配置 JdbcTemplate 或 Hibernate SessionFactory
- 配置 TransactionManager
- 配置组件扫描

第5步:打 WAR 包,部署到外部 Tomcat

整个过程:30-60 分钟,还没写一行业务代码!

而用 SpringBoot 呢?写一个启动类、配几行 yml、引一个 starter 依赖,就能跑了。

这背后的秘密,就是自动配置。

SpringBoot 的诞生

2012 年 10 月,Spring 官方在 GitHub 上收到一个 issue:

"Improved support for containerless web application architectures"

(改进对无容器 Web 应用架构的支持)

这个 issue 引发了 Spring 团队的深入思考:能不能让开发者不再为配置操心,把精力集中在业务逻辑上?

2014 年 4 月,Spring Boot 1.0 正式发布,它的核心目标就是:

复制代码
SpringBoot 要解决的四大问题:
═══════════════════════════════════════

问题1:配置繁琐 → 解决方案:自动配置(Auto-Configuration)
- 根据引入的依赖,自动创建需要的 Bean
- 提供合理的默认值,开箱即用
- 不满意可以随时覆盖

问题2:依赖管理混乱 → 解决方案:Starter 机制
- 一个 starter 打包所有相关依赖
- 版本号由 SpringBoot 统一管理
- 引入 spring-boot-starter-web 就够了,不用操心要哪些 jar

问题3:部署麻烦 → 解决方案:内嵌服务器
- 内嵌 Tomcat/Jetty/Undertow
- java -jar 直接运行
- 不需要外部 Tomcat

问题4:搭建耗时 → 解决方案:Spring Initializr
- 网页勾选需要的功能
- 自动生成项目骨架
- 30 秒创建一个可运行的项目

自动配置的设计哲学

自动配置是 SpringBoot 解决上述问题的核心武器,它的设计哲学可以概括为三大原则:

复制代码
原则1:约定优于配置(Convention over Configuration)
- 提供合理的默认值
- 只在需要时才自定义
- 减少决策成本

原则2:用户优先
- 你配了就用你的
- 你没配就用默认的
- 永远不强制覆盖用户的选择

原则3:按需加载
- 不是把所有东西都配上
- 而是根据你引入的依赖决定配什么
- 引了 Redis 依赖才配 Redis,没引就不配

用一句话总结自动配置的由来:

Spring 框架虽然强大,但配置太繁琐。SpringBoot 的自动配置机制就是为了让开发者从重复的配置工作中解放出来,实现"引入依赖即可使用"的开发体验。

理解了这个背景,接下来我们就来看看自动配置到底是什么、它是怎么实现的。


2. 什么是自动配置?

上一节我们知道了 SpringBoot 为什么要搞自动配置------就是为了解决传统 Spring 配置太繁琐的问题。那自动配置到底是什么呢?别急,我们先从一个生活中的例子说起。

先从生活说起

想象一下,你要出差住酒店。你有两个选择:

选择A:住毛坯房(传统 Spring)。 房间是空的,你得自己搬家具进去------自己装灯泡、自己铺床单、自己接热水管、自己牵网线。虽然最终你可以把房间布置成你想要的样子,但在入住之前,你得花大量时间在"装修"上,根本没办法直接休息。

选择B:住精装修酒店(SpringBoot)。 你推开门,灯是亮的,床铺好了,空调在运转,WiFi 密码贴在桌上。你放下行李就可以开始工作了。如果你觉得空调温度不合适,调一下就行;如果你不喜欢枕头,跟前台说换一个就行。但大多数情况下,酒店给你准备的东西已经够用了。

这就是自动配置的核心思想------你不需要从零开始配置每一个组件,SpringBoot 已经帮你准备好了合理的默认配置。你只需要在不满意的时候去调整就行。

更具体地说:自动配置(Auto-Configuration)就是 SpringBoot 根据你引入的依赖,自动帮你创建和配置所需的 Bean,让你不用手动写配置就能直接使用。

比如,你在 pom.xml 中引入了 spring-boot-starter-web,SpringBoot 就会自动帮你配置好 DispatcherServletViewResolver、内嵌 Tomcat 等一系列 Web 开发所需的组件。你引入了 spring-boot-starter-data-redis,它就自动帮你配好 RedisTemplate。你什么都不需要写,引入依赖就能用。

官方定义

Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added.

--- Spring Boot 官方文档 - Auto-configuration

翻译过来就是:SpringBoot 自动配置会根据你添加的 jar 依赖,尝试自动配置你的 Spring 应用。

注意这里的"尝试"二字------它不是强制的。如果你自己配了,SpringBoot 就不会覆盖你的配置。这体现了自动配置的一个重要原则:用户优先

核心思想:约定优于配置(Convention over Configuration)

自动配置背后有一个非常重要的设计理念,叫做约定优于配置 。简单来说就是:与其让开发者配置每一个细节,不如提供一套合理的默认值。开发者只需要在默认值不满足需求时才去修改。

我们来看几个具体的例子:

  • 端口号 :SpringBoot 约定内嵌 Tomcat 默认端口是 8080。你什么都不配,启动后就是 8080。想改?在 application.yml 里写一行 server.port=9090 就行了。
  • Web 框架 :你引入了 spring-boot-starter-web,SpringBoot 就约定你要开发 Web 应用,自动配置好 Spring MVC 的所有组件。你什么都不配,Controller 写了就能访问。想自定义 MVC 行为?写一个 WebMvcConfigurer 实现类就行。
  • 数据库 :你引入了 MySQL 驱动加 spring-boot-starter-data-jpa,SpringBoot 就约定你要用 JPA 操作数据库,自动配置好 DataSourceEntityManagerFactory。你只需要在 application.yml 里告诉它数据库地址和密码就够了。

看到规律了吗?SpringBoot 总是先给你一个"开箱即用"的默认方案,你觉得不合适再去改。 这大大减少了开发者的配置工作量。


3. 从一个问题开始:为什么 SpringBoot 不需要写 XML?

说了这么多理论,你可能还是没有直观的感受。那我们来做一个最直接的对比:同样一个功能,传统 Spring 要写多少配置?SpringBoot 又要写多少?看完你就明白自动配置帮你省了多少事。

传统 Spring 的配置地狱

在没有 SpringBoot 之前,一个简单的 Web 项目需要大量配置。下面这些 XML 你看着可能头疼,但在以前,每个 Spring 项目都要手写这些东西:

xml 复制代码
<!-- web.xml ------ 配置 DispatcherServlet -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- spring-mvc.xml ------ 配置 MVC 组件 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<mvc:annotation-driven/>
<context:component-scan base-package="com.example"/>

<!-- applicationContext.xml ------ 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven/>

光是搭个基础框架就要写几十行 XML,还没开始写业务逻辑呢!

SpringBoot 的极简体验

同样的功能,在 SpringBoot 中:

java 复制代码
// 整个启动类就这么几行
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
yaml 复制代码
# application.yml 只需要写数据库连接信息
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: 123456
xml 复制代码
<!-- pom.xml 引入依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

DispatcherServlet、ViewResolver、DataSource、TransactionManager 全部自动配置好了!

看到差距了吗?传统 Spring 需要写几十行 XML 配置三四个文件,而 SpringBoot 只需要一个启动类加几行 yml。那些我们曾经手动配置的东西,SpringBoot 全部自动帮我们搞定了。

那么问题来了:SpringBoot 是怎么做到的?它怎么知道要配置哪些东西?

答案就藏在 @SpringBootApplication 这个注解里。从这里开始,我们就要进入自动配置的"内部世界"了。


4. @SpringBootApplication------一切的起点

每个 SpringBoot 项目都有一个启动类,上面标着 @SpringBootApplication。你可能每天都在写这个注解,但有没有想过,这一个注解到底做了什么?

注解拆解

@SpringBootApplication 其实是一个组合注解,也就是说,它把好几个注解合并成了一个。我们打开它的源码看一下:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration    // ① 标记这是一个配置类
@EnableAutoConfiguration    // ② 开启自动配置(核心!)
@ComponentScan(...)         // ③ 组件扫描
public @interface SpringBootApplication {
    // ...
}

可以看到,它本质上等价于同时使用了三个注解。我们来逐个看看它们各自负责什么:

第一个:@SpringBootConfiguration 这个注解其实很简单,点进去你会发现它就是 @Configuration 的马甲。它的作用就是告诉 Spring:"当前这个启动类本身也是一个配置类。"这样你可以直接在启动类里写 @Bean 方法来注册 Bean,虽然实际开发中我们很少这么做。

第二个:@ComponentScan 这个注解负责"组件扫描",也就是扫描启动类所在包以及所有子包下的 Spring 组件。你写的 @Controller@Service@Repository@Component 之所以能被 Spring 发现并管理,靠的就是这个注解。这也是为什么启动类要放在项目的根包下------如果放错了位置,扫描范围就不对了,你写的组件就扫不到。

第三个:@EnableAutoConfiguration 这才是今天的主角!它是自动配置的核心开关。正是因为它的存在,SpringBoot 才知道要去加载那些自动配置类,帮你自动创建各种 Bean。整个自动配置的流程,都从这个注解开始触发。

用一个生活比喻来理解:@SpringBootApplication 就像酒店的"一键入住"按钮。按下去之后,@SpringBootConfiguration 告诉系统"这是一个房间";@ComponentScan 自动识别你带来的行李(你写的代码);@EnableAutoConfiguration 自动配置房间设施(灯、空调、WiFi 等)。

接下来,我们重点来看 @EnableAutoConfiguration 内部到底做了什么。


5. @EnableAutoConfiguration------自动配置的核心开关

@EnableAutoConfiguration 是整个自动配置机制的"总开关"。只要你的启动类上有 @SpringBootApplication(它内部包含了 @EnableAutoConfiguration),自动配置就会被激活。那这个注解内部到底藏了什么呢?我们来看它的源码:

源码分析

java 复制代码
@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 {};
}

源码很短,但信息量很大。除了 excludeexcludeName(用来手动排除某些自动配置类,后面会讲),最重要的是两个注解:@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)。我们分别来看。

① @AutoConfigurationPackage------记住你的"家在哪"

java 复制代码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    // ...
}

这个注解的名字翻译过来就是"自动配置包"。它的作用很简单:把启动类所在的包路径记录下来,告诉 Spring 和其他组件"你的项目代码在哪个包下面"。

为什么需要记住这个信息呢?因为有些组件需要知道你的项目根包在哪里。最典型的就是 Spring Data JPA ------它需要扫描你的 @Entity 实体类,但它怎么知道你的实体类放在哪个包下面呢?就是通过 @AutoConfigurationPackage 记录的包路径来找的。

举个例子:假设你的启动类在 com.example.myapp 包下,@AutoConfigurationPackage 就会记住这个路径。然后 JPA 就知道:"我应该去 com.example.myapp 及其子包下扫描 @Entity 实体类。"如果没有这个注解,JPA 就不知道去哪找你的实体类,启动时就会报错。

② @Import(AutoConfigurationImportSelector.class)------真正的核心

这才是自动配置最最关键的一步!

@Import 是 Spring 提供的注解,作用是把一个类导入到 Spring 容器中。这里它导入的是 AutoConfigurationImportSelector,这个类名翻译过来就是"自动配置导入选择器"。

那这个"选择器"具体做了什么呢?简单说,它做了三件事:

  1. 找清单:去指定位置读取一份"自动配置类清单",上面列着所有候选的自动配置类(大约 150 多个)
  2. 做筛选 :根据各种条件注解(如 @ConditionalOnClass)过滤掉不需要的,只保留真正适用于你项目的配置类
  3. 导入容器:把筛选后的配置类导入 Spring 容器,让它们生效

最终,150 多个候选类经过层层筛选后,通常只有 10-20 个会真正生效。

用酒店的比喻来说:AutoConfigurationImportSelector 就是酒店的"设施管理员"。酒店有一份完整的设施清单(空调、WiFi、电视、冰箱、保险箱......),但管理员会根据客人的实际需求来决定哪些设施要开启。你带了笔记本电脑?那就开 WiFi。你没有要求加床?那就不加。最终客人的房间里只会出现他需要的设施。

工作流程

把上面的过程串起来,完整的自动配置流程是这样的:

应用启动时,@SpringBootApplication 中的 @EnableAutoConfiguration 被激活,它通过 @ImportAutoConfigurationImportSelector 引入。这个选择器开始工作:首先从配置文件中读取所有候选的自动配置类(约 150+ 个),然后去重,接着排除用户手动指定不要的类(通过 exclude 属性),再根据各个配置类上的条件注解(@ConditionalOnClass@ConditionalOnMissingBean 等)进行过滤,最终只把真正需要的 10-20 个配置类导入到 Spring 容器中。

复制代码
自动配置的完整流程:
═══════════════════════════════════════

① 应用启动,@EnableAutoConfiguration 生效
         │
         ▼
② @Import 引入 AutoConfigurationImportSelector
         │
         ▼
③ 选择器开始工作:
   │
   ├── 读取配置清单 → 获得 150+ 个候选配置类
   │
   ├── 去重 + 排除用户指定排除的类
   │
   ├── 条件注解过滤 → 只保留条件满足的
   │
   └── 最终导入到 Spring 容器 → 通常只有 10-20 个生效

那么问题来了:这份"配置清单"到底存放在哪里?里面写了什么?


6. spring.factories / AutoConfiguration.imports------配置清单从哪来

上一节我们知道了 AutoConfigurationImportSelector 会去读取一份"自动配置类清单"。那这份清单到底放在哪里?长什么样子?

配置清单的存放位置

这份清单其实就是一个普通的文本文件,存放在 SpringBoot 的 jar 包里面。你可以把它理解为一份"菜单"------上面列着 SpringBoot 能够提供的所有自动配置类。SpringBoot 在不同版本中,清单的存放方式有所不同:

SpringBoot 2.x:spring.factories

在 SpringBoot 2.x 中,清单放在 META-INF/spring.factories 文件里,采用的是 Properties 格式(也就是 key=value 的写法)。你可以在 spring-boot-autoconfigure.jar 这个 jar 包里找到它:

properties 复制代码
# spring-boot-autoconfigure.jar 中的 spring.factories(节选)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
  org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
  org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
  ... 共 100 多个

可以看到,它用 EnableAutoConfiguration 作为 key,value 是一长串用逗号分隔的类名。这些就是所有候选的自动配置类。

SpringBoot 3.x:AutoConfiguration.imports(新方式)

从 SpringBoot 3.x 开始,清单换了一个新的位置和格式。文件路径变成了 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,格式也简化了------每行写一个类名就行:

复制代码
# AutoConfiguration.imports(节选)
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

是不是清爽多了?每行一个类名,一目了然。

理解配置清单的作用

用生活中的例子来说,这份配置清单就像酒店的设施总目录 。上面列着酒店能提供的所有设施:空调、WiFi、电视、冰箱、保险箱、加湿器......但这并不意味着每个房间都会装上所有设施。实际装哪些,取决于客人的需求------也就是后面要讲的"条件注解"来决定。

为什么 SpringBoot 3.x 换了新方式?

你可能会好奇:旧方式用得好好的,为什么要换?主要有三个原因:

第一,旧文件太杂。 spring.factories 不仅存放自动配置类,还存放 ListenerInitializer 等各种类型的扩展。所有东西堆在一个文件里,又长又乱,维护起来很麻烦。

第二,加载效率低。 Spring 启动时要解析整个 spring.factories 文件,包括那些和自动配置完全无关的内容。虽然最终大部分配置类不会生效,但解析这步是跳不过的。

第三,格式不够直观。 Properties 的 key=value\ 格式,一长串类名用逗号和反斜杠连接,可读性很差。

新方式把自动配置类单独拎出来放在一个专门的文件里,每行一个类名,职责单一、格式清晰、加载更快。这是一个很好的改进。

不过你也不用太纠结版本差异,因为两种方式的核心原理完全相同 ------都是提供一份候选自动配置类的清单给 AutoConfigurationImportSelector 去读取。

好了,现在我们知道了清单上有 100 多个自动配置类。但你的项目不可能全都用到------你没引入 Redis 依赖,RedisAutoConfiguration 就不应该生效。那 SpringBoot 是怎么决定哪些配置该生效、哪些该跳过的呢?答案就是条件注解


7. 条件注解------精准控制配置是否生效

为什么需要条件注解?

上一节我们知道了配置清单上有 100 多个自动配置类。但这里有一个问题:你的项目不可能把所有技术都用上。比如你只是做一个简单的 Web 项目,根本没用 Redis,那 RedisAutoConfiguration 就不应该生效;你没用 MongoDB,那 MongoAutoConfiguration 也不应该生效。

如果 150 多个配置类全部生效,不但浪费资源,还可能因为找不到依赖而报错。所以 SpringBoot 需要一种机制来做"筛选"------根据你项目的实际情况,决定哪些配置该生效、哪些该跳过。

这个筛选机制就是条件注解(Conditional Annotations)。

你可以把条件注解理解为"门卫"。每个自动配置类的门口都站着一个门卫,只有满足特定条件才放行。不满足条件?对不起,这个配置类直接跳过,不会生效。

核心条件注解一览

SpringBoot 提供了好几种条件注解,每种负责检查不同的条件。我们来逐个认识一下:

@ConditionalOnClass ------ 检查类路径上是否有某个类。这是最常用的条件注解。它的逻辑很简单:你引入了某个 jar 包(也就是类路径上有这个 jar 中的类),对应的自动配置才生效。比如你引入了 spring-boot-starter-data-redis,类路径上就有 RedisOperations 这个类,RedisAutoConfiguration 才会生效。没引入?跳过。

@ConditionalOnMissingBean ------ 检查容器中是否没有某个 Bean。这是实现"用户优先"的关键。它的逻辑是:如果用户已经自己定义了一个同类型的 Bean,那自动配置就不再创建默认的了。这样用户的配置永远优先于自动配置。

@ConditionalOnProperty ------ 检查配置文件中是否有某个属性。比如只有你在 application.yml 里配了 spring.datasource.url,数据源自动配置才会生效。

@ConditionalOnBean ------ 检查容器中是否已经有某个 Bean。比如事务管理器的自动配置依赖于数据源的存在------只有数据源 Bean 存在了,才去配置事务管理器。

@ConditionalOnMissingClass ------ 和 @ConditionalOnClass 相反,类路径上没有某个类时才生效。

@ConditionalOnWebApplication ------ 当前是 Web 应用时才生效。

@ConditionalOnNotWebApplication ------ 当前不是 Web 应用时才生效。

用生活中的例子来理解:条件注解就像酒店的智能设施管理系统@ConditionalOnClass 就是"客人带了笔记本电脑,才开 WiFi";@ConditionalOnMissingBean 就是"客人没自带枕头,才提供酒店枕头";@ConditionalOnProperty 就是"客人预定时勾选了需要加床,才加床";@ConditionalOnBean 就是"床已经摆好了,才去铺床单"。

看一个真实的自动配置类

光讲概念可能还是有些抽象,我们来看一个真实的自动配置类------DataSourceAutoConfiguration(数据源自动配置),看看条件注解在实际代码中是怎么用的。不用害怕源码,我会逐行帮你解读:

java 复制代码
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
// ↑ 条件1:类路径上有 DataSource 类(引入了数据库相关依赖)
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
// ↑ 条件2:不是响应式数据库(没有 R2DBC)
@EnableConfigurationProperties(DataSourceProperties.class)
// ↑ 将 application.yml 中 spring.datasource.* 的配置绑定到 DataSourceProperties
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
          DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    // ↑ 条件3:你没有自己配置 DataSource Bean
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration {
        // 如果上面条件都满足,就自动配置一个内嵌数据库
    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class,
              DataSourceConfiguration.Tomcat.class,
              DataSourceConfiguration.Dbcp2.class,
              ... })
    protected static class PooledDataSourceConfiguration {
        // 或者自动配置一个连接池数据源(默认 HikariCP)
    }
}

条件判断的逻辑流程

看完源码,我们来梳理一下 DataSourceAutoConfiguration 的完整判断过程。SpringBoot 在加载这个配置类时,会像做问卷调查一样逐个检查条件:

第一关: 类路径上有 DataSource 类吗?换句话说,你有没有引入数据库相关的依赖?如果没有,说明你的项目根本不需要数据库,这个配置类直接跳过,什么都不做。

第二关: 你用的是不是响应式数据库(R2DBC)?如果是的话,传统的 JDBC 数据源配置就不适用了,交给专门的响应式配置类来处理。

第三关: 你自己有没有手动定义一个 DataSource Bean?如果你已经定义了,说明你对数据源有自己的配置需求,SpringBoot 就不再"多管闲事",直接用你的。

只有当以上三关都通过了 (有数据库依赖、不是响应式、用户没自定义),SpringBoot 才会帮你自动创建一个 HikariDataSource(默认的连接池),参数来自 application.ymlspring.datasource.* 的配置。

复制代码
DataSourceAutoConfiguration 的条件判断过程:
═══════════════════════════════════════

类路径上有 DataSource 类吗?(你引入了数据库依赖吗?)
├── 没有 → 跳过,不配置
└── 有   → 继续判断
         │
         ▼
是响应式数据库吗?
├── 是 → 跳过,交给响应式配置
└── 不是 → 继续判断
         │
         ▼
你自己定义了 DataSource Bean 吗?
├── 定义了 → 用你的,自动配置不生效
└── 没定义 → 自动创建 HikariDataSource
              参数来自 application.yml 的 spring.datasource.*

这就是"用户配了就用用户的,没配就用默认的"------约定优于配置的具体体现。 到这里,你应该能理解为什么 SpringBoot 既能"开箱即用",又不会"强制覆盖"你的配置了。


8. 源码追踪:自动配置的完整执行流程

前面几节我们已经理解了自动配置的"是什么"和"为什么",现在我们来看看"怎么做"------也就是从你调用 main() 方法开始,SpringBoot 内部到底经历了怎样的过程才完成自动配置的。

这一节会涉及一些源码,但你不需要记住每一行代码。重要的是理解整个调用链的逻辑脉络------从哪里开始、经过哪些关键节点、最终做了什么事情。

入口:SpringApplication.run()

一切的起点就是你启动类中的 main 方法:

java 复制代码
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);  // 一切从这里开始
    }
}

当你运行这个 main 方法时,SpringApplication.run() 被调用,SpringBoot 的整个启动流程就开始了。

第一步:创建应用上下文并刷新

SpringApplication.run() 内部会做很多事情,但和自动配置最相关的是两步:创建应用上下文(ApplicationContext),然后刷新它。"刷新"这个词听起来很轻描淡写,但其实这一步做了非常多的事情,包括扫描 Bean 定义、处理注解、创建 Bean 等等。自动配置就是在刷新的过程中被触发的。

java 复制代码
// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
    // ... 省略其他逻辑
    
    // 创建应用上下文
    ConfigurableApplicationContext context = createApplicationContext();
    
    // 刷新上下文(自动配置在这里被触发!)
    refreshContext(context);   // ⭐ 关键步骤
    
    return context;
}

第二步:refresh() 中处理配置类

刷新上下文时,Spring 会调用一系列"后处理器"来处理各种注解。其中有一个叫 invokeBeanFactoryPostProcessors() 的方法,它会触发 ConfigurationClassPostProcessor,这个后处理器专门负责解析 @Configuration 类上的各种注解------包括 @Import

java 复制代码
// AbstractApplicationContext.java
public void refresh() {
    // ... 省略前面的步骤
    
    // 调用 BeanFactory 后处理器(这里会处理 @Import 注解)
    invokeBeanFactoryPostProcessors(beanFactory);  // ⭐ 关键步骤
    
    // ... 后续步骤
}

简单说就是:当 Spring 解析你的启动类时,发现了 @SpringBootApplication → 发现了 @EnableAutoConfiguration → 发现了 @Import(AutoConfigurationImportSelector.class)。于是 Spring 就把 AutoConfigurationImportSelector 拿出来执行。

第三步:AutoConfigurationImportSelector 开始工作

AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口。Spring 调用它的 selectImports() 方法,让它"选择"要导入哪些配置类。而这个方法的核心逻辑在 getAutoConfigurationEntry() 中:

java 复制代码
// AutoConfigurationImportSelector.java
public class AutoConfigurationImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        // 获取自动配置条目------真正干活的方法
        AutoConfigurationEntry autoConfigurationEntry = 
            getAutoConfigurationEntry(annotationMetadata);  // ⭐ 核心方法
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

第四步:getAutoConfigurationEntry()------核心中的核心

这个方法就是整个自动配置的"大脑"。它的逻辑很清晰,我在每一步后面都加了详细说明:

java 复制代码
// AutoConfigurationImportSelector.java
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AnnotationMetadata annotationMetadata) {
    
    // 1. 检查自动配置是否被禁用
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }

    // 2. 获取注解属性(exclude 等)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 3. 获取所有候选自动配置类 ⭐
    //    从 META-INF/spring/...AutoConfiguration.imports 文件中读取
    List<String> configurations = getCandidateConfigurations(
        annotationMetadata, attributes);
    // 此时 configurations 大约有 150+ 个类名

    // 4. 去重
    configurations = removeDuplicates(configurations);

    // 5. 获取需要排除的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);

    // 6. 移除排除的类
    configurations.removeAll(exclusions);

    // 7. 根据条件注解过滤 ⭐
    //    只保留条件满足的配置类
    configurations = getConfigurationClassFilter()
        .filter(configurations);
    // 过滤后通常只剩下 10-20 个

    // 8. 触发自动配置导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);

    // 9. 返回最终的自动配置类列表
    return new AutoConfigurationEntry(configurations, exclusions);
}

我们来用大白话梳理一下这段代码的逻辑:

  1. 先检查用户有没有通过 spring.boot.enableautoconfiguration=false 把自动配置关掉。如果关掉了,直接返回空,什么都不配。
  2. 去读取配置清单文件,拿到所有候选的自动配置类名------大约 150 多个。
  3. 去重(防止不同 jar 包中注册了同一个配置类)。
  4. 检查用户有没有通过 @SpringBootApplication(exclude = ...) 手动排除某些配置类,如果有就移除。
  5. 最关键的一步:条件过滤。 逐个检查每个配置类上的条件注解(@ConditionalOnClass@ConditionalOnMissingBean 等),把条件不满足的全部过滤掉。经过这一步,通常只剩下 10-20 个真正需要的配置类。
  6. 把最终的配置类列表返回给 Spring,Spring 会把这些配置类注册为 Bean 定义,它们内部的 @Bean 方法就会被执行,各种组件 Bean 就这样被自动创建并放入了容器中。

第五步:getCandidateConfigurations()------读取配置清单

上面第 3 步中的"读取配置清单"具体是怎么读的呢?来看 getCandidateConfigurations() 方法:

java 复制代码
// AutoConfigurationImportSelector.java
protected List<String> getCandidateConfigurations(
        AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    // SpringBoot 3.x:从 AutoConfiguration.imports 文件中读取
    // SpringBoot 2.x:从 spring.factories 文件中读取
    List<String> configurations = ImportCandidates.load(
        AutoConfiguration.class, getBeanClassLoader())
        .getCandidates();
    
    // 断言:如果没找到任何配置类,说明配置有问题
    Assert.notEmpty(configurations,
        "No auto configuration classes found in "
        + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports");
    
    return configurations;
}

逻辑很简单:调用 ImportCandidates.load() 方法去读取 AutoConfiguration.imports 文件(或 spring.factories),把里面列出的类名全部加载进来。如果一个都没找到,就抛异常------说明你的 SpringBoot 依赖有问题。

完整流程图

把上面所有步骤串起来,自动配置的完整源码调用链如下:

复制代码
自动配置的源码执行流程:
═══════════════════════════════════════

main() 
  → SpringApplication.run()
    → refreshContext()
      → refresh()
        → invokeBeanFactoryPostProcessors()
          → ConfigurationClassPostProcessor
            → 解析 @SpringBootApplication
              → 解析 @EnableAutoConfiguration
                → 处理 @Import(AutoConfigurationImportSelector.class)
                  → selectImports() / getAutoConfigurationEntry()
                    │
                    ├── getCandidateConfigurations()
                    │   → 读取 AutoConfiguration.imports
                    │   → 获得 150+ 个候选配置类
                    │
                    ├── removeDuplicates()
                    │   → 去重
                    │
                    ├── getExclusions()
                    │   → 移除 @SpringBootApplication(exclude=...) 指定的类
                    │
                    └── filter()
                        → 根据 @ConditionalOnClass 等条件注解过滤
                        → 最终剩下 10-20 个生效的配置类
                          │
                          ▼
                    这些配置类被注册为 Bean 定义
                    → 它们内部的 @Bean 方法被执行
                    → 各种组件 Bean 被自动创建并放入容器

9. 实战:手写一个自定义 Starter

前面我们花了大量篇幅讲自动配置的原理,可能你已经对概念有了不错的理解,但"看懂"和"会用"之间还差一步。所以这一节,我们来动手写一个自定义的 Starter。

写完之后你会发现:原来那些看起来很"魔法"的东西,自己也能实现!

我们的目标是实现一个简单的"问候服务"Starter。用户引入这个 Starter 后,不需要做任何配置,就可以直接注入一个 GreetingService 来使用。当然,如果用户想自定义问候语的前缀和后缀,也可以通过 application.yml 来配置。

第一步:定义功能类

首先,我们要写这个 Starter 提供的核心功能。这就是一个普通的 Java 类,没有任何 Spring 注解:

java 复制代码
/**
 * 问候服务------这是我们 Starter 提供的核心功能
 */
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;
    }
}

第二步:定义配置属性类

接下来,我们需要一个类来接收用户在 application.yml 中写的配置。SpringBoot 提供了 @ConfigurationProperties 注解来做这件事------它会自动把配置文件中指定前缀的属性值绑定到这个类的字段上:

java 复制代码
/**
 * 配置属性类------绑定 application.yml 中的配置
 */
@ConfigurationProperties(prefix = "my.greeting")
public class GreetingProperties {

    /**
     * 问候语前缀,默认值 "Hello, "
     */
    private String prefix = "Hello, ";

    /**
     * 问候语后缀,默认值 "!"
     */
    private String suffix = "!";

    // getter / setter
    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; }
}

第三步:编写自动配置类

这是整个 Starter 最核心的部分。自动配置类就是告诉 SpringBoot:"当满足什么条件时,帮用户自动创建哪些 Bean。"我们在这个类上加上条件注解,确保只在合适的时候才生效:

java 复制代码
/**
 * 自动配置类------这是 Starter 的核心
 */
@AutoConfiguration
@ConditionalOnClass(GreetingService.class)
// ↑ 只有 GreetingService 类存在时才生效
@EnableConfigurationProperties(GreetingProperties.class)
// ↑ 启用配置属性绑定
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    // ↑ 只有用户没有自己定义 GreetingService 时,才创建默认的
    public GreetingService greetingService(GreetingProperties properties) {
        return new GreetingService(properties.getPrefix(), properties.getSuffix());
    }
}

第四步:注册自动配置类

写完自动配置类后,还有最关键的一步------你得告诉 SpringBoot 这个配置类的存在。还记得前面讲的"配置清单"吗?你需要把你的自动配置类注册到清单中,否则 AutoConfigurationImportSelector 找不到它,什么都不会发生。

SpringBoot 3.x 方式:

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

复制代码
com.example.greeting.GreetingAutoConfiguration
SpringBoot 2.x 方式:

创建文件 src/main/resources/META-INF/spring.factories

properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.greeting.GreetingAutoConfiguration

第五步:使用这个 Starter

到这里,我们的 Starter 就开发完了。接下来把它打成 jar 包发布。其他项目只需要在 pom.xml 中引入这个 jar 依赖,就可以直接使用了------不需要做任何额外的配置:

java 复制代码
// 引入依赖后,直接注入使用
@RestController
public class HelloController {

    @Autowired
    private GreetingService greetingService;
    // 不需要任何配置,自动配置已经帮你创建好了!

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        return greetingService.greet(name);
        // 默认输出:Hello, 张三!
    }
}
yaml 复制代码
# 想自定义?在 application.yml 中覆盖即可
my:
  greeting:
    prefix: "你好,"
    suffix: ",欢迎光临!"
# 输出变成:你好,张三,欢迎光临!
java 复制代码
// 想完全替换?自己定义一个 GreetingService Bean
@Configuration
public class MyConfig {
    
    @Bean
    public GreetingService greetingService() {
        // 因为 @ConditionalOnMissingBean 的存在
        // 自动配置类检测到你已经定义了,就不会再创建默认的
        return new GreetingService("Hi, ", "~");
    }
}

Starter 的工作原理图

复制代码
自定义 Starter 的工作原理:
═══════════════════════════════════════

用户项目引入你的 Starter jar
         │
         ▼
SpringBoot 启动时扫描 AutoConfiguration.imports
         │
         ▼
发现 GreetingAutoConfiguration
         │
         ▼
检查条件注解:
├── @ConditionalOnClass(GreetingService.class)
│   → GreetingService 类存在?✅ 存在(在 jar 里)
│
└── @ConditionalOnMissingBean
    → 用户自己定义了 GreetingService?
    ├── 定义了 → 跳过,用用户自己的
    └── 没定义 → 创建默认的 GreetingService Bean
                   │
                   ▼
              读取 GreetingProperties
              (来自 application.yml 中的 my.greeting.*)
                   │
                   ▼
              new GreetingService(prefix, suffix)
              注册到 Spring 容器中
                   │
                   ▼
              用户可以直接 @Autowired 使用了!

10. 自动配置的覆盖与排除

自动配置虽然方便,但总有一些场景下默认配置不能满足你的需求。这时候你就需要"覆盖"或"排除"某些自动配置。SpringBoot 提供了多种方式来让你掌握控制权,下面我们逐一来看。

三种覆盖方式

方式一:通过配置文件覆盖默认值

这是最简单也最常用的方式。自动配置类通常会读取 application.yml 中的配置来设置参数,你只需要在配置文件里写上你想要的值,就能覆盖默认值:

yaml 复制代码
# application.yml
server:
  port: 9090              # 覆盖默认端口 8080
spring:
  datasource:
    url: jdbc:mysql://...  # 指定数据库连接
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss  # 覆盖 JSON 日期格式
方式二:通过自定义 Bean 覆盖自动配置

如果你需要更深层次的控制,可以自己定义一个同类型的 Bean。因为大多数自动配置类都用了 @ConditionalOnMissingBean,所以只要你定义了,自动配置就会自动让步:

java 复制代码
@Configuration
public class MyDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // 自己创建 DataSource
        // 因为 DataSourceAutoConfiguration 有 @ConditionalOnMissingBean
        // 检测到你已经定义了,就不会再自动创建
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        ds.setUsername("root");
        ds.setPassword("123456");
        ds.setMaximumPoolSize(20);  // 自定义连接池大小
        return ds;
    }
}
方式三:排除某个自动配置

有时候你不只是想覆盖,而是完全不想让某个自动配置类生效。比如你的项目引入了数据库依赖但暂时不想用,可以直接排除数据源的自动配置。SpringBoot 提供了两种排除方式:

java 复制代码
// 方式 3a:通过注解排除(推荐)
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    RedisAutoConfiguration.class
})
public class MyApplication {
    // ...
}

// 方式 3b:通过配置文件排除
// application.yml
// spring:
//   autoconfigure:
//     exclude:
//       - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
//       - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

覆盖优先级

你可能会问:如果我同时用了多种覆盖方式,SpringBoot 以谁为准?规则很简单------用户的配置永远优先于自动配置的默认值。具体的优先级从高到低是:

  1. 你自己定义的 @Bean ------ 最高优先级。只要你定义了同类型的 Bean,自动配置就直接让步。
  2. application.yml 中的配置 ------ 覆盖自动配置中使用的默认属性值。
  3. 自动配置类的默认值 ------ 最低优先级,只有你什么都没配的时候才会用到。

DataSource 举例来说:SpringBoot 会先看你有没有自己定义 DataSource Bean,有的话就用你的;没有的话再看你有没有在 application.yml 里配数据库连接信息,有的话就用你配的参数自动创建一个 HikariDataSource;如果你连数据库连接信息都没配,它就尝试使用内嵌数据库(如 H2)。


11. 调试技巧:如何查看哪些自动配置生效了

在实际开发中,你经常会遇到这样的困惑:"为什么我引入了某个依赖,但对应的功能没有生效?"或者"为什么启动时报错说找不到某个 Bean?"这时候你就需要查看自动配置的实际执行情况------哪些配置类生效了、哪些没生效、为什么没生效。

SpringBoot 提供了好几种方式来帮你排查这类问题:

方法一:开启 debug 模式(最常用)

这是最简单也最常用的调试方式。只需要在 application.yml 中加一行配置:

yaml 复制代码
# application.yml
debug: true

启动后,控制台会打印一份非常详细的自动配置评估报告 (CONDITIONS EVALUATION REPORT)。这份报告分为两部分:Positive matches(生效的配置)和 Negative matches(未生效的配置),并且会告诉你每个配置类生效或不生效的具体原因:

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

Positive matches:(生效的自动配置)
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource'
      
   WebMvcAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'Servlet', 'DispatcherServlet'

Negative matches:(未生效的自动配置)
-----------------
   RedisAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'RedisConnectionFactory'

   MongoAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'MongoClient'

比如从上面的报告中你可以清楚地看到:DataSourceAutoConfiguration 之所以生效,是因为类路径上找到了 javax.sql.DataSource 类;RedisAutoConfiguration 没有生效,是因为类路径上没有 RedisConnectionFactory 类(你没引入 Redis 依赖)。这对排查问题非常有帮助。

方法二:使用 Actuator 端点

如果你的应用已经在运行,不方便重启看控制台日志,可以使用 Spring Boot Actuator 提供的 HTTP 端点来实时查看。首先需要引入 Actuator 依赖:

xml 复制代码
<!-- 引入 Actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然后在配置文件中暴露 conditions 端点:

yaml 复制代码
# 暴露 conditions 端点
management:
  endpoints:
    web:
      exposure:
        include: conditions

启动后访问 http://localhost:8080/actuator/conditions,你会看到一份 JSON 格式的条件评估报告,内容和 debug 模式打印的一样,但更方便在浏览器中查看和搜索。

方法三:使用 IDE 查看

如果你用的是 IntelliJ IDEA,还有一种更直观的方式:启动 SpringBoot 应用后,在 Run 面板中点击 Beans 标签,就可以看到所有已注册的 Bean,包括自动配置创建的。你还可以搜索特定的 Bean,查看它是由哪个配置类创建的。这对于理解"某个 Bean 是从哪来的"非常有帮助。


12. 面试题精选与回答思路

SpringBoot 自动配置是面试中的高频考点。下面精选了 6 道常见面试题,每道都给出了完整的回答思路。如果你认真读完了前面的内容,这些问题应该都能轻松回答。

题目一:SpringBoot 自动配置的原理是什么?

回答思路

SpringBoot 自动配置的核心原理是:通过 @SpringBootApplication 注解中的 @EnableAutoConfiguration,触发 AutoConfigurationImportSelector 去读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列出的所有候选自动配置类,然后通过条件注解(如 @ConditionalOnClass@ConditionalOnMissingBean)过滤出真正需要的配置类,最终将这些配置类中定义的 Bean 注册到 Spring 容器中。

简单来说就是三步:找到配置清单 → 条件过滤 → 注册 Bean

题目二:@ConditionalOnMissingBean 有什么作用?

回答思路

@ConditionalOnMissingBean 表示"当容器中不存在某个 Bean 时,才创建当前 Bean"。这是实现"约定优于配置"的关键机制。

举例:DataSourceAutoConfiguration 中用 @ConditionalOnMissingBean(DataSource.class) 修饰了自动创建 DataSource 的方法。如果开发者自己定义了 DataSource Bean,自动配置就不会生效;如果没有自定义,自动配置会帮你创建一个默认的。

本质上就是"用户优先"原则:你配了用你的,没配用默认的。

题目三:spring.factories 和 AutoConfiguration.imports 有什么区别?

回答思路

两者的功能相同,都是存放自动配置类的清单。区别在于:

  • spring.factories 是 SpringBoot 2.x 的方式,采用 Properties 格式,所有类型的扩展(不仅是自动配置)都放在一个文件里
  • AutoConfiguration.imports 是 SpringBoot 3.x 引入的新方式,每行一个类名,只存放自动配置类

新方式更清晰、更快,职责更单一。SpringBoot 3.x 仍然兼容 spring.factories,但推荐使用新方式。

题目四:如何自定义一个 SpringBoot Starter?

回答思路

自定义 Starter 需要以下步骤:

  1. 创建功能类:实现 Starter 要提供的核心功能
  2. 创建配置属性类 :用 @ConfigurationProperties 绑定外部配置
  3. 创建自动配置类 :用 @AutoConfiguration 标注,内部用 @Bean 创建功能 Bean,配合 @ConditionalOnClass@ConditionalOnMissingBean 等条件注解
  4. 注册配置类 :在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中写入自动配置类的全类名
  5. 打包发布:其他项目引入这个 jar 后,SpringBoot 会自动发现并加载

题目五:自动配置类加载了 100 多个,会不会影响性能?

回答思路

不会有明显影响。虽然候选的自动配置类有 100 多个,但大部分在条件过滤阶段就被排除了(比如 @ConditionalOnClass 检测到类路径上没有对应的类,直接跳过)。最终生效的通常只有 10-20 个。

而且条件注解的过滤非常轻量,只是做类路径检查和 Bean 存在性检查,不会真正实例化那些不需要的配置类。所以性能损耗可以忽略不计。

题目六:你能说一下 @SpringBootApplication 包含哪些注解吗?

回答思路

@SpringBootApplication 是一个组合注解,包含三个核心注解:

  1. @SpringBootConfiguration :本质是 @Configuration,标记当前类是一个配置类
  2. @EnableAutoConfiguration :开启自动配置,触发 AutoConfigurationImportSelector 加载自动配置类
  3. @ComponentScan:组件扫描,扫描启动类所在包及子包下的 Spring 组件

其中 @EnableAutoConfiguration 是实现自动配置的核心。


13. 总结

恭喜你读到了这里!让我们回顾一下整篇文章的核心内容。

自动配置的完整知识地图

复制代码
SpringBoot 自动配置知识地图:
═══════════════════════════════════════

核心思想:约定优于配置

@SpringBootApplication
├── @SpringBootConfiguration   → 配置类标记
├── @ComponentScan             → 扫描你的代码
└── @EnableAutoConfiguration   → 自动配置开关
    └── @Import(AutoConfigurationImportSelector.class)
        │
        ├── 读取配置清单
        │   ├── SpringBoot 2.x: spring.factories
        │   └── SpringBoot 3.x: AutoConfiguration.imports
        │
        ├── 条件过滤
        │   ├── @ConditionalOnClass       → 类存在才生效
        │   ├── @ConditionalOnMissingBean → 用户没定义才生效
        │   ├── @ConditionalOnProperty    → 配置了属性才生效
        │   └── @ConditionalOnBean        → 依赖的Bean存在才生效
        │
        └── 注册 Bean
            → 自动配置类中的 @Bean 方法被执行
            → 组件注册到 Spring 容器

覆盖策略:
├── 自定义 @Bean          → 最高优先级
├── application.yml 配置  → 覆盖默认属性
└── 自动配置默认值         → 兜底方案

用一句话总结

如果有人问你"SpringBoot 自动配置是什么",你可以这样回答:

SpringBoot 自动配置的本质就是:根据你引入的依赖,从预定义的配置清单中找出需要的配置类,通过条件注解按需加载,自动为你创建和注册 Bean。你什么都不配就能用默认的,想自定义随时可以覆盖。

理解自动配置的四个层次

最后,我把对自动配置的理解分为四个层次,供你对照自己目前在哪个阶段:

Level 1:会用。 知道加上 @SpringBootApplication 就能启动,引入 starter 就能用。大多数初级开发者在这个层次。

Level 2:知道原理。 知道自动配置是通过 @EnableAutoConfigurationAutoConfigurationImportSelector → 读取配置清单 → 条件过滤这一套流程实现的。知道 @ConditionalOnClass@ConditionalOnMissingBean 的作用。读完本文后,你应该至少在这个层次。

Level 3:能自定义。 能自己写一个 Starter,理解配置属性绑定、条件注解的组合使用,能排查自动配置不生效的问题。

Level 4:能读源码。 能从 SpringApplication.run() 一路追踪到 getAutoConfigurationEntry(),理解 Spring 容器刷新过程中配置类是怎么被解析和注册的。这是架构师级别的理解。


参考资料

相关推荐
曹牧2 小时前
Spring MVC配置文件
java·spring·mvc
小江的记录本2 小时前
【分布式】分布式一致性协议:2PC/3PC、Paxos、Raft、ZAB 核心原理、区别(2026必考Raft)
java·前端·分布式·后端·安全·面试·系统架构
北风toto2 小时前
RestTemplate 的入门使用,直接给上作者的项目Demo
java
疯狂打码的少年2 小时前
JDK 7、8、13 和 20区别深度了解
java·开发语言
钝挫力PROGRAMER2 小时前
Java中如何优雅管理接口的多个实现
java·设计模式
古方路杰出青年2 小时前
学习笔记1:Python FastAPI极简后端API示例解析
笔记·后端·python·学习·fastapi
迷藏4942 小时前
# 发散创新:基于Python的自动特征工程实战与深度优化在机器学习
java·开发语言·python·机器学习
星晨雪海2 小时前
查询区域列表并统计点位数量
java
Seven972 小时前
用300行代码手写一个mini版的Tomcat
java