SpringBoot 自动配置是如何实现的?
面试考察点
- 注解体系理解:面试官不仅仅是想知道你听过
@EnableAutoConfiguration这个名字,更想知道你能不能把它拆开,说清楚每个注解的作用和它们之间的组合关系。 - SPI 机制掌握:Spring Boot 自动配置的核心加载方式是 SPI(
Spring Factories机制),面试官想考察你是否理解这个类加载机制。 - 条件过滤原理:自动配置类那么多,不可能全部生效。面试官想知道你是否清楚
@Conditional系列注解的工作原理,以及 "用户自定义优先" 这个设计理念。
核心答案
Spring Boot 自动配置的实现可以概括为 三个关键步骤:
| 步骤 | 做了什么 | 核心组件 |
|---|---|---|
| 1. 入口触发 | @EnableAutoConfiguration 注解触发自动配置 | @SpringBootApplication 内含 |
| 2. 全量加载 | 通过 SPI 机制从 spring.factories 全量加载自动配置类 | AutoConfigurationImportSelector |
| 3. 条件过滤 | 通过 @ConditionalOnXxx 注解按条件过滤,满足的才生效 | 各种 Condition 实现 |
一句话总结:先全量加载,再按条件过滤,用户自定义优先。
深度解析
一、从入口注解开始
一切从 @SpringBootApplication 这个注解开始。它是一个复合注解,拆开看:
// @SpringBootApplication 源码(简化)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootConfiguration // ① 等同于 @Configuration,标记配置类
@EnableAutoConfiguration // ② 核心!开启自动配置
@ComponentScan( // ③ 组件扫描
excludeFilters = {
@Filter(type = FilterType.CUSTOM,
classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
// ...
}
三个注解各司其职:
@SpringBootConfiguration:本质就是@Configuration,表示当前类是一个配置类,可以声明@Bean方法。@ComponentScan:扫描当前启动类所在包及子包下的@Component、@Service、@Controller等注解。这就是为什么你的业务类必须放在启动类同包或子包下。@EnableAutoConfiguration:这才是自动配置的关键入口,下面重点展开。
二、@EnableAutoConfiguration 做了什么?
// @EnableAutoConfiguration 源码(简化)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AutoConfigurationPackage // 自动配置包路径
@Import(AutoConfigurationImportSelector.class) // 核心!导入选择器
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY =
"spring.boot.enableautoconfiguration";
// 排除指定的自动配置类
Class<?>[] exclude() default {};
// 按名称排除
String[] excludeName() default {};
}
关键就在 @Import(AutoConfigurationImportSelector.class) 这一行。AutoConfigurationImportSelector 是整个自动配置的 "调度中心",它负责把所有符合条件的自动配置类加载到 Spring 容器中。
三、自动配置的完整加载流程
上图是自动配置从触发到生效的完整链路,按阶段拆解:
- 阶段一(入口触发):
@EnableAutoConfiguration注解通过@Import导入AutoConfigurationImportSelector,这是整个流程的起点。 - 阶段二(全量加载):
AutoConfigurationImportSelector调用SpringFactoriesLoader,扫描 classpath 下所有 jar 包的META-INF/spring.factories文件,读出所有以EnableAutoConfiguration为 key 的配置类全限定名。这一步是 "全量加载",有多少加载多少,不挑食。 - 阶段三(去重排除):对加载到的配置类去重,排除通过
exclude属性明确指定的类,排除通过spring.autoconfigure.exclude配置项指定的类。 - 阶段四(条件过滤):这是最关键的一步。遍历剩余的候选配置类,逐个检查
@ConditionalOnXxx注解,只有满足条件的才会被注册到容器中。 - 阶段五(注册生效):通过条件过滤的配置类正式生效,其内部的
@Bean方法会被执行,向 Spring 容器注册对应的 Bean。
四、以 DataSourceAutoConfiguration 为例
光说流程太抽象,拿一个具体的自动配置类来看看:
// DataSourceAutoConfiguration.java(简化版源码)
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // ① classpath 有 DataSource 类
@ConditionalOnMissingBean(DataSource.class) // ② 容器中没有自定义 DataSource
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(DataSourceProperties properties) {
// 根据 application.yml 中的配置创建数据源
return DataSourceBuilder.create()
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
这个类只有满足 两个条件 才会生效:
- 条件一:
@ConditionalOnClass(DataSource.class)—— classpath 上存在DataSource类,说明你引入了数据库相关的依赖(比如spring-boot-starter-jdbc或 MyBatis)。如果你项目里压根没引入数据库依赖,这个配置类直接跳过。 - 条件二:
@ConditionalOnMissingBean(DataSource.class)—— 容器中还没有DataSource类型的 Bean。如果你自己手动定义了一个@Bean DataSource,那 Spring Boot 就不再自动创建了,用户自定义优先。
这就是 "约定优于配置" 的实现方式:
- 你什么都不配 → Spring Boot 用默认值帮你创建(约定)
- 你自己配了 → Spring Boot 退让,用你的(用户优先)
- 你引入了依赖 → 自动配置生效;没引入 → 自动配置不生效
五、@Conditional 系列注解一览
Spring Boot 提供了一整套条件注解,面试的时候至少能说出几个常用的:
| 注解 | 条件 | 典型场景 |
|---|---|---|
@ConditionalOnClass | classpath 中存在指定类 | 引入了某个依赖才生效 |
@ConditionalOnMissingClass | classpath 中不存在指定类 | 没引入某个依赖才生效 |
@ConditionalOnBean | 容器中已存在指定 Bean | 某个 Bean 已注册才生效 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean | 最常用,用户没配才生效 |
@ConditionalOnProperty | 配置文件中有指定属性 | spring.datasource.url 配了才生效 |
@ConditionalOnWebApplication | 当前是 Web 应用 | 只有 Web 环境才生效 |
@ConditionalOnNotWebApplication | 当前不是 Web 应用 | 非 Web 环境才生效 |
@ConditionalOnExpression | SpEL 表达式为 true | 复杂条件判断 |
其中 @ConditionalOnMissingBean 出现频率最高,它是实现 "用户自定义优先" 的关键。
六、自定义一个 Starter
理解了自动配置原理,自定义 Starter 就是顺理成章的事。三步搞定:
第一步:写一个自动配置类
@AutoConfiguration
@ConditionalOnClass(MyService.class) // 有 MyService 才生效
@ConditionalOnProperty(prefix = "my.starter",
name = "enabled", havingValue = "true",
matchIfMissing = true) // 默认生效
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties props) {
return new MyService(props.getPrefix());
}
}
第二步:注册到 spring.factories(Spring Boot 2.7 之前)
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
Spring Boot 2.7+ 推荐用新方式:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
第三步:引入依赖就自动生效
其他项目只要在 pom.xml 中引入这个 Starter 的依赖,MyService 就会自动注册到容器中,零配置。
面试高频追问
-
追问一:自动配置类一定会生效吗?
- 不一定。自动配置类必须通过
@ConditionalOnXxx的条件检查才会生效。比如DataSourceAutoConfiguration需要 classpath 上有数据库驱动;RedisAutoConfiguration需要引入spring-boot-starter-data-redis。这也是为什么引入不同 Starter 会自动拥有不同能力的原因。
- 不一定。自动配置类必须通过
-
追问二:如何排查某个自动配置类有没有生效?
- 在
application.yml中加上debug: true,启动时会在控制台打印CONDITIONS EVALUATION REPORT,列出哪些自动配置类生效了、哪些没生效以及原因。也可以用/actuator/conditions端点(Actuator)来查看。
- 在
-
追问三:
@ComponentScan和@EnableAutoConfiguration有什么区别?@ComponentScan扫描的是你项目里自己写的@Component、@Service、@Controller等注解标记的类,范围是启动类所在包及子包。@EnableAutoConfiguration加载的是第三方 jar 包里的xxxAutoConfiguration类,通过 SPI 机制从spring.factories中读取。两者互补:一个管你自己的类,一个管框架的类。
-
追问四:Spring Boot 3.x 的自动配置有什么变化?
- 最大的变化是
spring.factories中EnableAutoConfiguration的注册方式被废弃了,改为使用AutoConfiguration.imports文件。另外自动配置类从@Configuration改为使用@AutoConfiguration注解(Spring Boot 3.0 新增),语义更明确。
- 最大的变化是
常见面试变体
- "说一下
@SpringBootApplication注解的组成?" - "
@ConditionalOnMissingBean是什么意思?有什么用?" - "如何自定义一个 Spring Boot Starter?"
- "
spring.factories文件的作用是什么?"
记忆口诀
注解入口 → SPI 全量加载 → 条件过滤 → 满足的生效
核心设计理念:约定优于配置,用户自定义优先(
@ConditionalOnMissingBean)
总结
Spring Boot 自动配置的本质就是:@EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector,利用 SPI 机制从 spring.factories 全量加载候选配置类,再通过 @ConditionalOnXxx 系列注解按条件过滤,最终只有满足条件的配置类才会向容器注册 Bean。其中 @ConditionalOnMissingBean 保证了 "用户自定义优先" 的核心设计。面试时把 "全量加载 + 条件过滤 + 用户优先" 这条线讲清楚就够了。
