定位 :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 就会自动帮你配置好 DispatcherServlet、ViewResolver、内嵌 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.
翻译过来就是: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 操作数据库,自动配置好DataSource和EntityManagerFactory。你只需要在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 {};
}
源码很短,但信息量很大。除了 exclude 和 excludeName(用来手动排除某些自动配置类,后面会讲),最重要的是两个注解:@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,这个类名翻译过来就是"自动配置导入选择器"。
那这个"选择器"具体做了什么呢?简单说,它做了三件事:
- 找清单:去指定位置读取一份"自动配置类清单",上面列着所有候选的自动配置类(大约 150 多个)
- 做筛选 :根据各种条件注解(如
@ConditionalOnClass)过滤掉不需要的,只保留真正适用于你项目的配置类 - 导入容器:把筛选后的配置类导入 Spring 容器,让它们生效
最终,150 多个候选类经过层层筛选后,通常只有 10-20 个会真正生效。
用酒店的比喻来说:AutoConfigurationImportSelector 就是酒店的"设施管理员"。酒店有一份完整的设施清单(空调、WiFi、电视、冰箱、保险箱......),但管理员会根据客人的实际需求来决定哪些设施要开启。你带了笔记本电脑?那就开 WiFi。你没有要求加床?那就不加。最终客人的房间里只会出现他需要的设施。
工作流程
把上面的过程串起来,完整的自动配置流程是这样的:
应用启动时,@SpringBootApplication 中的 @EnableAutoConfiguration 被激活,它通过 @Import 把 AutoConfigurationImportSelector 引入。这个选择器开始工作:首先从配置文件中读取所有候选的自动配置类(约 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 不仅存放自动配置类,还存放 Listener、Initializer 等各种类型的扩展。所有东西堆在一个文件里,又长又乱,维护起来很麻烦。
第二,加载效率低。 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.yml 中 spring.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);
}
我们来用大白话梳理一下这段代码的逻辑:
- 先检查用户有没有通过
spring.boot.enableautoconfiguration=false把自动配置关掉。如果关掉了,直接返回空,什么都不配。 - 去读取配置清单文件,拿到所有候选的自动配置类名------大约 150 多个。
- 去重(防止不同 jar 包中注册了同一个配置类)。
- 检查用户有没有通过
@SpringBootApplication(exclude = ...)手动排除某些配置类,如果有就移除。 - 最关键的一步:条件过滤。 逐个检查每个配置类上的条件注解(
@ConditionalOnClass、@ConditionalOnMissingBean等),把条件不满足的全部过滤掉。经过这一步,通常只剩下 10-20 个真正需要的配置类。 - 把最终的配置类列表返回给 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 以谁为准?规则很简单------用户的配置永远优先于自动配置的默认值。具体的优先级从高到低是:
- 你自己定义的
@Bean------ 最高优先级。只要你定义了同类型的 Bean,自动配置就直接让步。 application.yml中的配置 ------ 覆盖自动配置中使用的默认属性值。- 自动配置类的默认值 ------ 最低优先级,只有你什么都没配的时候才会用到。
用 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 需要以下步骤:
- 创建功能类:实现 Starter 要提供的核心功能
- 创建配置属性类 :用
@ConfigurationProperties绑定外部配置- 创建自动配置类 :用
@AutoConfiguration标注,内部用@Bean创建功能 Bean,配合@ConditionalOnClass和@ConditionalOnMissingBean等条件注解- 注册配置类 :在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中写入自动配置类的全类名- 打包发布:其他项目引入这个 jar 后,SpringBoot 会自动发现并加载
题目五:自动配置类加载了 100 多个,会不会影响性能?
回答思路:
不会有明显影响。虽然候选的自动配置类有 100 多个,但大部分在条件过滤阶段就被排除了(比如
@ConditionalOnClass检测到类路径上没有对应的类,直接跳过)。最终生效的通常只有 10-20 个。而且条件注解的过滤非常轻量,只是做类路径检查和 Bean 存在性检查,不会真正实例化那些不需要的配置类。所以性能损耗可以忽略不计。
题目六:你能说一下 @SpringBootApplication 包含哪些注解吗?
回答思路:
@SpringBootApplication是一个组合注解,包含三个核心注解:
- @SpringBootConfiguration :本质是
@Configuration,标记当前类是一个配置类- @EnableAutoConfiguration :开启自动配置,触发
AutoConfigurationImportSelector加载自动配置类- @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:知道原理。 知道自动配置是通过 @EnableAutoConfiguration → AutoConfigurationImportSelector → 读取配置清单 → 条件过滤这一套流程实现的。知道 @ConditionalOnClass 和 @ConditionalOnMissingBean 的作用。读完本文后,你应该至少在这个层次。
Level 3:能自定义。 能自己写一个 Starter,理解配置属性绑定、条件注解的组合使用,能排查自动配置不生效的问题。
Level 4:能读源码。 能从 SpringApplication.run() 一路追踪到 getAutoConfigurationEntry(),理解 Spring 容器刷新过程中配置类是怎么被解析和注册的。这是架构师级别的理解。
参考资料: