📌 PDF :大白话说Java面试题 --- 06_Spring篇
第23题:说说SpringBoot 自动配置原理?
📚 回答:
- 核心考点 : SpringBoot 自动配置 的原理远不止"读取 spring.factories 文件"这么简单。大厂面试中,面试官期望你深入理解
@SpringBootApplication的三层组合设计 (@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan)、AutoConfigurationImportSelector的完整加载链路 (selectImports→getAutoConfigurationEntry→getCandidateConfigurations→SpringFactoriesLoader)、DeferredImportSelector的延迟加载机制 (确保自动配置在用户自定义 Bean 之后处理)、条件注解的底层实现 (@Conditional+Condition接口 +SpringBootCondition)、以及 Spring Boot 3.0 后spring.factories到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports的迁移。面试官真正想判断的是:你是否能从 SPI 机制、条件装配、加载顺序三个维度建立完整的自动配置认知体系。
1. @SpringBootApplication 的三层组合设计
@SpringBootApplication 是一个组合注解,由三个核心注解构成,各司其职:
| 注解 | 等效注解 | 核心职责 | 源码体现 |
|---|---|---|---|
@SpringBootConfiguration |
@Configuration |
标识主启动类为配置类,允许注册 Bean 或导入其他配置 | @Configuration 的别名,仅语义区分 |
@EnableAutoConfiguration |
无(Spring Boot 特有) | 开启自动配置,是自动装配的"总开关" | @AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class) |
@ComponentScan |
@ComponentScan |
组件扫描,默认扫描当前包及其子包 | excludeFilters 排除 TypeExcludeFilter 和 AutoConfigurationExcludeFilter |
java
// @SpringBootApplication 源码
@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 {
// exclude/excludeName:排除特定自动配置类
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
关键设计 :excludeFilters 中的 AutoConfigurationExcludeFilter 确保被 @Configuration 标记的自动配置类不会被 @ComponentScan 重复扫描,避免 Bean 重复注册。citation:0
2. @EnableAutoConfiguration 的核心机制
@EnableAutoConfiguration 由两个子注解组成,分别解决"包扫描范围"和"自动配置类加载"两个问题:
-
2.1
@AutoConfigurationPackage将主启动类所在包注册为自动配置的基础包,供后续组件扫描使用:java@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) // 关键:导入 Registrar public @interface AutoConfigurationPackage { }AutoConfigurationPackages.Registrar将主类所在包名保存到BasePackagesBean 中,供 MyBatis-Plus 的@MapperScan、JPA 的@EntityScan等使用。 -
2.2
@Import(AutoConfigurationImportSelector.class)导入AutoConfigurationImportSelector,这是自动配置的核心入口。它实现了DeferredImportSelector接口(继承自ImportSelector),区别于普通ImportSelector的关键在于:延迟到用户自定义配置类处理完毕后再执行,确保自动配置的 Bean 可以被用户自定义 Bean 覆盖。citation:1
3. AutoConfigurationImportSelector 的完整加载链路
AutoConfigurationImportSelector 的 selectImports() 方法是自动配置类加载的入口:
java
// AutoConfigurationImportSelector.selectImports() 源码
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查自动配置开关(spring.boot.enableautoconfiguration,默认 true)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 加载自动配置元数据(spring-autoconfigure-metadata.properties)
AutoConfigurationMetadata autoConfigurationMetadata =
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 3. 获取自动配置条目(核心)
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
// 4. 返回配置类全限定名数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getAutoConfigurationEntry() 的 6 步过滤链路:
java
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// Step 1: 检查自动配置开关
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// Step 2: 获取 @EnableAutoConfiguration 的 exclude/excludeName 属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// Step 3: 读取 spring.factories 获取所有候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// Step 4: 去重(多个 jar 可能有重复配置)
configurations = removeDuplicates(configurations);
// Step 5: 处理排除项(exclude/excludeName)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// Step 6: 条件过滤(@ConditionalOnClass 等条件注解评估)
configurations = filter(configurations, autoConfigurationMetadata);
// Step 7: 触发自动配置导入事件(用于监控和扩展)
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
citation:2
4. SpringFactoriesLoader 与 SPI 机制
getCandidateConfigurations() 通过 SpringFactoriesLoader 读取 META-INF/spring.factories 文件:
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 加载 META-INF/spring.factories 中 EnableAutoConfiguration 键对应的配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories");
return configurations;
}
spring.factories 文件格式(Spring Boot 2.x):
properties
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
Spring Boot 3.0 重大变更 :从 spring.factories 迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
# 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.data.redis.RedisAutoConfiguration
变更原因:
spring.factories是键值对格式,一个文件包含多种 SPI 类型(自动配置、监听器、初始化器等),解析效率低;- 新格式每个文件只包含一种类型的全限定名,解析更快,且支持按行注释;
- Spring Boot 3.0 仍兼容
spring.factories,但推荐使用新格式。citation:3
5. 条件注解的底层实现
自动配置类通过 @Conditional 系列注解实现"按需加载",避免所有配置类全部生效:
| 条件注解 | 底层 Condition 实现 | 判断逻辑 | 典型场景 |
|---|---|---|---|
@ConditionalOnClass |
OnClassCondition |
类路径是否存在指定类 | DataSourceAutoConfiguration 判断是否存在 DataSource.class |
@ConditionalOnMissingBean |
OnBeanCondition |
容器中是否不存在指定 Bean | 用户未自定义 DataSource 时才自动配置 |
@ConditionalOnProperty |
OnPropertyCondition |
配置属性是否满足条件 | spring.datasource.url 存在时才配置数据源 |
@ConditionalOnWebApplication |
OnWebApplicationCondition |
是否为 Web 应用 | WebMvcAutoConfiguration 仅在 Web 环境下生效 |
@ConditionalOnJava |
OnJavaCondition |
JDK 版本是否满足 | 特定 JDK 版本才启用某些功能 |
@ConditionalOnResource |
OnResourceCondition |
类路径是否存在指定资源 | 配置文件存在时才加载 |
@ConditionalOnClass 的底层实现:
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class) // 关联 Condition 实现类
public @interface ConditionalOnClass {
Class<?>[] value() default {}; // 直接指定类
String[] name() default {}; // 通过类全限定名(避免 ClassNotFound)
}
OnClassCondition 的匹配逻辑 :使用 ClassNameFilter 在类加载器中查找类,不会真正加载类(避免触发静态初始化块),仅检查类是否存在。
java
// 以 DataSourceAutoConfiguration 为例
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) // 类路径有 DataSource
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class) // 用户未自定义 DataSource
@ConditionalOnProperty(name = "spring.datasource.url") // 配置了数据库 URL
static class EmbeddedDatabaseConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create(properties.getClassLoader())
.type(HikariDataSource.class)
.driverClassName(properties.determineDriverClassName())
.url(properties.determineUrl())
.username(properties.determineUsername())
.password(properties.determinePassword())
.build();
}
}
}
citation:4
6. 自动配置的执行顺序控制
-
6.1
DeferredImportSelector的延迟机制AutoConfigurationImportSelector实现DeferredImportSelector而非普通ImportSelector,确保自动配置类在用户自定义@Configuration之后处理。这样用户可以通过自定义 Bean 覆盖自动配置的默认 Bean。 -
6.2
@AutoConfigureOrder和@AutoConfigureAfter控制自动配置类之间的加载顺序:注解 作用 示例 @AutoConfigureOrder指定绝对顺序(数值越小优先级越高) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE - 10)@AutoConfigureAfter在指定配置类之后加载 @AutoConfigureAfter(DataSourceAutoConfiguration.class)@AutoConfigureBefore在指定配置类之前加载 @AutoConfigureBefore(WebMvcAutoConfiguration.class)java// MyBatisAutoConfiguration 必须在 DataSourceAutoConfiguration 之后 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @ConditionalOnSingleCandidate(DataSource.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) // 数据源配置完成后才加载 public class MybatisAutoConfiguration { // ... }
7. 自定义 Starter 的完整步骤
基于自动配置原理,自定义 Starter 需要遵循标准规范:
| 步骤 | 操作 | 文件/代码 |
|---|---|---|
| Step 1 | 创建 Maven 工程,引入 spring-boot-autoconfigure 依赖 |
pom.xml |
| Step 2 | 编写自动配置类,使用 @Conditional 控制生效条件 |
XxxAutoConfiguration.java |
| Step 3 | 编写属性配置类(@ConfigurationProperties) |
XxxProperties.java |
| Step 4 | 创建 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
每行一个自动配置类全限定名 |
| Step 5 | 在其他项目引入 starter 依赖即可自动生效 | pom.xml |
java
// 示例:自定义线程池 Starter
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ThreadPoolExecutor.class)
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户未自定义时才创建
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolProperties properties) {
return new ThreadPoolExecutor(
properties.getCorePoolSize(),
properties.getMaxPoolSize(),
properties.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(properties.getQueueCapacity())
);
}
}
// 属性配置类
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
private int corePoolSize = 4;
private int maxPoolSize = 8;
private long keepAliveTime = 60;
private int queueCapacity = 100;
// getters/setters...
}
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfig.ThreadPoolAutoConfiguration
8. 面试官追问与高分回答模板
-
追问 1:"请详细描述 SpringBoot 自动配置的原理?"
低分回答 :"通过
@EnableAutoConfiguration读取spring.factories文件,加载里面的自动配置类。"(太浅,没有触及核心链路)高分回答 : > "SpringBoot 自动配置的核心链路分为 5 步: > 1. 入口 :
@SpringBootApplication组合了@EnableAutoConfiguration,后者通过@Import(AutoConfigurationImportSelector.class)导入选择器; > 2. 延迟加载 :AutoConfigurationImportSelector实现DeferredImportSelector,确保自动配置在用户自定义@Configuration之后处理,用户 Bean 可覆盖默认配置; > 3. 读取候选配置 :selectImports()→getAutoConfigurationEntry()→getCandidateConfigurations()通过SpringFactoriesLoader读取所有 jar 包中META-INF/spring.factories(Spring Boot 2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中的自动配置类全限定名; > 4. 过滤去重 :去重 → 处理exclude/excludeName排除项 → 通过OnClassCondition、OnBeanCondition等条件注解评估,只有类路径存在指定类、容器中不存在指定 Bean 等条件满足时才保留; > 5. 注册 Bean :将过滤后的配置类注册到 Spring 容器,生成相应 Bean。 > 关键设计是 SPI 机制 + 条件装配 + 延迟加载 的三位一体。" -
追问 2:"
DeferredImportSelector和普通ImportSelector有什么区别?为什么自动配置要用它?"高分回答 : > "两者的核心区别在于 执行时机 : > -
ImportSelector:在 Spring 容器刷新阶段的ConfigurationClassParser处理时立即执行,与普通@Configuration同步处理; > -DeferredImportSelector:延迟到所有普通配置类(用户自定义的@Configuration、@ComponentScan等)处理完毕后再执行。 > 自动配置必须使用DeferredImportSelector的原因是:确保用户自定义 Bean 的优先级高于自动配置 Bean 。例如用户自定义了DataSource,自动配置的DataSourceAutoConfiguration中的@ConditionalOnMissingBean(DataSource.class)会判断为 false,从而跳过默认数据源创建。如果自动配置先执行,用户后定义,就会导致 Bean 覆盖冲突或重复定义。" -
追问 3:"
spring.factories和 SPI 机制有什么关系?Spring Boot 3.0 为什么改了格式?"高分回答 : > "
spring.factories是 Spring Boot 对 Java SPI(Service Provider Interface)机制的扩展实现。Java 标准 SPI 通过META-INF/services/接口全限定名文件发现服务实现,Spring Boot 将其扩展为META-INF/spring.factories的键值对格式,支持多种 SPI 类型(自动配置、监听器、初始化器等)。 > Spring Boot 3.0 改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports的原因: > 1. 解析效率 :旧格式一个文件包含多种 SPI 类型,需要逐行解析键值对;新格式每个文件只包含一种类型的全限定名列表,解析更快; > 2. 可读性 :新格式支持按行注释(#开头),旧格式是 properties 格式,注释方式不一致; > 3. 模块化 :每种 SPI 类型独立文件,职责更清晰。 > Spring Boot 3.0 仍兼容spring.factories,但推荐使用新格式。" -
追问 4:"
@ConditionalOnClass是怎么判断类是否存在的?会加载类吗?"高分回答 : > "
@ConditionalOnClass的底层实现是OnClassCondition类,它使用ClassNameFilter在类加载器中查找类,不会真正加载类 。 > 具体实现: > 1.OnClassCondition.getOutcomes()接收候选配置类和条件注解; > 2. 通过ClassNameFilter.MATCH调用ClassUtils.forName()的变体,使用ClassLoader.loadClass()的非初始化模式 (不执行静态初始化块); > 3. 如果类存在则返回ConditionOutcome.match(),不存在返回ConditionOutcome.noMatch()。 > 为什么必须避免加载类?因为某些依赖类可能引入了其他传递依赖,如果直接加载会触发NoClassDefFoundError,导致应用启动失败。通过只检查类名是否存在,可以在不触发类加载的情况下安全判断。 > 另外@ConditionalOnClass的name属性支持通过字符串指定类全限定名,进一步避免编译期依赖。" -
追问 5:"如果我想让自动配置失效,有哪些方法?"
高分回答 : > "让自动配置失效有 5 种方法,按优先级从高到低: > 1.
@SpringBootApplication(exclude = XxxAutoConfiguration.class):在启动类上排除特定自动配置类,最常用; > 2.spring.autoconfigure.exclude配置 :在application.yml中配置排除项,无需修改代码; > 3.@EnableAutoConfiguration(excludeName = "xxx"):通过类全限定名字符串排除,适用于类路径可能不存在的情况; > 4. 用户自定义同名 Bean :利用@ConditionalOnMissingBean,用户定义了 Bean 后自动配置的条件不满足,自动跳过; > 5. 条件注解不满足 :如@ConditionalOnProperty(name = "spring.datasource.url"),如果不配置该属性,自动配置类不会生效。 > 生产环境中推荐用spring.autoconfigure.exclude配置化排除,避免代码硬编码。" -
追问 6:"SpringBoot 自动配置和 Spring 的
@Configuration有什么区别?"高分回答 : > "两者的核心区别在于 触发时机和条件控制 : > - 普通
@Configuration:只要被@ComponentScan扫描到就会生效,无条件限制,是"显式配置"; > - 自动配置类 :通过spring.factories或imports文件注册,被AutoConfigurationImportSelector统一加载,且必须通过@Conditional系列注解的条件评估才会生效,是"隐式配置"。 > 从设计哲学上看: > -@Configuration是 开发者主动声明 的配置,Spring 负责执行; > - 自动配置是 框架主动猜测 的配置,Spring Boot 根据类路径和已有 Bean 推断开发者需要什么,按需加载 。 > 自动配置类通常也会标注@Configuration,但还会附加@ConditionalOnClass、@AutoConfigureAfter等自动配置特有的注解。"
9. 方案选型速查表
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 排除特定自动配置 | spring.autoconfigure.exclude |
配置化,无需改代码 |
| 自定义 Starter | 遵循 5 步规范 | AutoConfiguration.imports + @ConditionalOnMissingBean |
| 控制自动配置顺序 | @AutoConfigureAfter/@AutoConfigureBefore |
确保依赖配置先加载 |
| 覆盖默认 Bean | 用户自定义同名 Bean | 利用 @ConditionalOnMissingBean 机制 |
| 条件化加载配置 | @ConditionalOnProperty |
根据配置属性开关功能 |
| Spring Boot 3.0+ 项目 | 使用新 imports 格式 |
解析更快,职责更清晰 |
| 兼容旧版本 | 保留 spring.factories |
Spring Boot 3.0 仍兼容 |
💡 面试官想要的满分总结:
SpringBoot 自动配置的本质是 "约定优于配置" 的工程实践,其核心机制可以概括为 SPI 发现 + 条件装配 + 延迟加载 三位一体:
- SPI 发现 :
AutoConfigurationImportSelector通过SpringFactoriesLoader读取所有 jar 包中的spring.factories(2.x)或AutoConfiguration.imports(3.x),获取候选自动配置类列表;- 条件装配 :通过
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等条件注解,利用OnClassCondition、OnBeanCondition等Condition实现类进行过滤,只加载满足条件的配置;- 延迟加载 :
DeferredImportSelector确保自动配置在用户自定义配置之后处理,用户 Bean 优先级高于默认 Bean。理解自动配置必须抓住三个关键点:
- 不是全部加载 :
spring.factories中的配置只是候选,最终生效的取决于条件注解评估;- 可以被覆盖 :用户自定义同名 Bean +
@ConditionalOnMissingBean机制,实现"默认有,自定义优先";- 顺序很重要 :
@AutoConfigureAfter确保依赖先初始化,如MybatisAutoConfiguration必须在DataSourceAutoConfiguration之后。最后记住:Spring Boot 3.0 的
imports文件格式变更不是 breaking change,而是性能优化。自定义 Starter 时务必使用@ConditionalOnMissingBean保护默认 Bean,避免与用户配置冲突。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯