Spring Boot 自动配置原理
候选人小许在面试阿里 P6 时,面试官问道:
"Spring Boot 的自动配置是怎么实现的?"
小许说:"通过 @EnableAutoConfiguration..."
面试官追问:"具体是怎么找到并加载配置的?"
小许说:"好像是通过 spring.factories..."
面试官继续追问:"spring.factories 里面写了什么?为什么需要它?"
小许答不上来了。
【面试官心理】
这道题我用来测试候选人对 Spring Boot 核心原理的理解。自动配置是 Spring Boot 最核心的特性,也是区分"Spring Boot 使用者"和"Spring Boot 理解者"的关键。能说清楚 spring.factories 和 AutoConfigurationImportSelector 的,基本都研究过 Spring Boot 源码。
一、核心问题 🔴
1.1 问题拆解
第一层:概念
- "Spring Boot 自动配置是什么?"
- "自动配置和手动配置有什么区别?"
第二层:原理
- "@EnableAutoConfiguration 是怎么工作的?"
- "spring.factories 文件里写了什么?"
- "AutoConfigurationImportSelector 做了什么?"
第三层:过程
- "自动配置的加载顺序是什么?"
- "@Conditional 是怎么控制配置生效的?"
第四层:定制
- "怎么排除某些自动配置?"
- "怎么自定义自动配置?"
1.2 ❌ 错误示范
候选人原话 A:"自动配置就是 Spring Boot 默认配置了很多 Bean,不用我们自己配了。"
问题诊断:
- 知道大概效果,但不理解原理
- 说不清 spring.factories 的作用
候选人原话 B:"spring.factories 里面是配置类的路径。"
问题诊断:
- 知道一部分,但说错了
- 实际上里面是 AutoConfiguration 类的全限定名
候选人原话 C:"自动配置不需要任何条件判断。"
问题诊断:
- 完全错误
- @Conditional 系列注解就是用来控制条件的
1.3 标准回答
P5 回答:核心流程
Spring Boot 自动配置的核心流程:
自动配置流程:
1. @SpringBootApplication
└─ @EnableAutoConfiguration
└─ @Import(AutoConfigurationImportSelector.class)
└─ selectImports()
└─ SpringFactoriesLoader.loadFactoryNames()
└─ 扫描所有 spring.factories
└─ 加载自动配置类
└─ @Conditional 判断是否生效
└─ 注册 Bean
1.4 追问升级
追问 1:spring.factories 详解
# spring-boot-autoconfigure/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
spring.factories 的格式:
- Key:
org.springframework.boot.autoconfigure.EnableAutoConfiguration
- Value:逗号分隔的 AutoConfiguration 类的全限定名
Spring Boot 2.7+ 的变化:
# Spring Boot 2.7+ 推荐使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
# spring.factories 仍然支持,但会显示弃用警告
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfigure.MyAutoConfiguration
com.example.autoconfigure.AnotherAutoConfiguration
追问 2:AutoConfigurationImportSelector
这是自动配置的核心类:
// AutoConfigurationImportSelector 实现了 DeferredImportSelector
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 获取所有自动配置类的名称
List<String> configurations = getCandidateConfigurations(
annotationMetadata, getAttributes(annotationMetadata));
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 获取需要排除的配置
Set<String> exclusions = getExclusions(annotationMetadata);
configurations.removeAll(exclusions);
// 4. 根据 @Conditional 判断是否生效
configurations = filter(configurations, autoConfigurationMetadata);
return configurations.toArray(new String[0]);
}
// 加载 spring.factories 中的配置
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
// SpringFactoriesLoader 是关键
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
return configurations;
}
}
// SpringFactoriesLoader
public final class SpringFactoriesLoader {
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 1. 扫描 classpath 下所有 spring.factories
// 2. 根据 factoryType 读取对应的值
// 3. 返回配置类名列表
}
}
追问 3:@Conditional 控制生效条件
AutoConfiguration 类使用 @Conditional 系列注解控制是否生效:
// DataSourceAutoConfiguration 示例
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
@ConditionalOnClass(EnableTransactionManager.class)
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
return new DataSourceTransactionManager(dataSource);
}
}
// @ConditionalOnProperty 示例
@Configuration
@ConditionalOnProperty(prefix = "spring.cache", name = "type", havingValue = "redis")
public class RedisCacheAutoConfiguration {
// 只有配置了 spring.cache.type=redis 才生效
}
// @ConditionalOnMissingBean 示例
@Configuration
@ConditionalOnMissingBean(UserService.class)
public class DefaultUserServiceAutoConfiguration {
// 只有容器中没有 UserService Bean 时才生效
// 这就是为什么用户自定义 Bean 可以覆盖自动配置
}
追问 4:配置顺序和覆盖规则
自动配置类的加载顺序由 @AutoConfigureBefore 和 @AutoConfigureAfter 控制:
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ConditionalOnClass({ EnableWebMvc.class })
public class WebMvcAutoConfiguration {
// 在 DataSourceAutoConfiguration 之后加载
}
@Configuration
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
public class WebMvcConfig {
// 在 WebMvcAutoConfiguration 之前加载
}
覆盖规则(优先级从高到低):
- 用户定义的
@Bean(@Configuration 类中的方法)
- 用户定义的
@Component(@Service、@Controller 等)
- 自动配置类中的
@Bean
- 自动配置类的默认行为
二、延伸问题 🟡
2.1 Spring Boot 2.7 的变化
Spring Boot 2.7 引入了新的自动配置文件位置:
Spring Boot 2.7 之前:
META-INF/spring.factories
└─ org.springframework.boot.autoconfigure.EnableAutoConfiguration=...
Spring Boot 2.7+:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
└─ 每行一个配置类路径
好处:
- 更简单的格式
- 支持按需加载(不需要一次性加载所有)
- IDE 提示更友好
2.2 排除自动配置
// 方式1:@SpringBootApplication 排除
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
// 方式2:配置文件排除
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
// 方式3:@EnableAutoConfiguration 排除
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
2.3 自定义自动配置
// 1. 创建自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
@AutoConfigureAfter(HttpEncodingAutoConfiguration.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
}
// 2. 定义配置属性
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String url = "http://default";
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
}
// 3. 注册(spring.factories 或 .imports)
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfigure.MyServiceAutoConfiguration
三、生产避坑
3.1 自动配置冲突
// 常见冲突:两个自动配置都往容器注册了同类型的 Bean
@Configuration
public class ConfigA {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@Configuration
public class ConfigB {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}
// 解决:@ConditionalOnMissingBean
@Configuration
public class ConfigA {
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper objectMapper() {
return new ObjectMapper(); // 只有不存在时才注册
}
}
3.2 条件判断不当导致的问题
// ❌ 错误:条件太严格
@ConditionalOnClass(JedisClient.class) // 依赖 JedisClient,可能不存在
public class RedisAutoConfiguration {
// 如果 JedisClient 不在 classpath,整个配置不生效
}
// ✅ 正确:使用可选依赖
@ConditionalOnClass(name = "redis.clients.jedis.JedisClient")
public class RedisAutoConfiguration {
// 类存在时才生效,不存在时静默跳过
}
四、工程选型
4.1 自动配置 vs 手动配置
4.2 何时需要自定义自动配置
- 封装内部框架:将内部框架封装为 Starter
- 团队共享配置:统一团队的默认配置
- 第三方库集成:封装第三方库的配置
五、面试总结
Spring Boot 自动配置是 Spring Boot 最核心的特性。
P5 候选人能说出"通过 @EnableAutoConfiguration 实现"。
P6 候选人能说清 spring.factories 的作用、@Conditional 的使用。
P7 候选人能说出 AutoConfigurationImportSelector 的完整流程、Spring Boot 2.7 的变化。
记住,自动配置的核心是"约定大于配置"——Spring Boot 帮我们做了很多默认配置,但允许我们覆盖。