Spring Boot 自动配置原理
Spring Boot 自动配置不是“魔法”,它的本质是:
Spring Boot 在启动时准备一批候选配置类,再根据 classpath、配置项、容器中已有 Bean、Web 环境等条件判断,决定哪些默认 Bean 应该注册到 Spring 容器里。
如果你只会说“约定优于配置”,面试只能答到表面。真正要理解自动配置,必须把这条链路讲清楚:
@SpringBootApplication
-> @EnableAutoConfiguration
-> AutoConfigurationImportSelector
-> 读取自动配置候选类
-> 条件注解筛选
-> 注册 BeanDefinition
-> refresh 阶段创建 Bean学习目标
学完本页,你应该能回答:
@SpringBootApplication为什么能启动自动配置。@EnableAutoConfiguration自己到底做了什么。- Boot 2.6及更早、Boot 2.7和Boot 3的自动配置候选声明有什么区别。
- 条件注解为什么是自动配置的核心。
- 为什么用户自定义 Bean 后,Boot 默认 Bean 会让位。
- 自动配置为什么最终仍然要回到 Spring IOC 和 Bean 生命周期。
- Starter 和自动配置是什么关系。
- 线上怎么排查某个自动配置为什么没生效。
如果你已经知道基础概念,想把自动配置、Starter、扩展点和生产排查串成一条完整链路,继续看 Spring Boot 自动配置与扩展点全过程。
从一个现象开始
创建一个最小 Web 项目:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}你没有手动配置 Tomcat、DispatcherServlet、JSON 转换器、参数解析器,但接口可以访问。这不是因为 Spring Boot 猜到了你的想法,而是因为:
- 你引入了
spring-boot-starter-web。 - classpath 里出现了 Spring MVC、Jackson、Tomcat 等类。
- 自动配置类判断条件成立。
- Spring Boot 注册了一批默认 Bean。
- 如果你自己定义 Bean,默认 Bean 会让位。
@SpringBootApplication 拆开看
@SpringBootApplication 是组合注解,核心可以理解成三部分:
| 注解 | 作用 | 如果没有会怎样 |
|---|---|---|
@SpringBootConfiguration | 表示这是一个 Spring 配置类 | 启动类不再作为配置类参与解析 |
@ComponentScan | 扫描当前包及子包组件 | Controller、Service 等不会自动注册 |
@EnableAutoConfiguration | 开启自动配置 | Web、数据源、Redis、MVC 等默认能力不会自动装配 |
流程图:
flowchart TD
A["启动类标注 @SpringBootApplication"] --> B["@SpringBootConfiguration"]
A --> C["@ComponentScan"]
A --> D["@EnableAutoConfiguration"]
B --> E["启动类作为配置类"]
C --> F["扫描业务组件"]
D --> G["导入自动配置候选类"]很多人把 @SpringBootApplication 等同于“启动注解”,这是不准确的。它真正做的是把配置类、组件扫描和自动配置三件事放在一起。
自动配置完整流程
自动配置不是在运行时到处扫描 jar 包,而是读取约定位置的自动配置清单。
flowchart TD
A["SpringApplication.run"] --> B["解析启动类注解"]
B --> C["@EnableAutoConfiguration"]
C --> D["AutoConfigurationImportSelector"]
D --> E["读取候选自动配置类"]
E --> F["去重和排除"]
F --> G["条件注解判断"]
G --> H{"条件是否匹配"}
H -- "否" --> I["跳过该自动配置"]
H -- "是" --> J["注册 BeanDefinition"]
J --> K["refresh 阶段创建 Bean"]关键点:
- 自动配置类先作为候选名单被导入。
- 并不是所有候选配置都会生效。
- 条件注解决定“是否装配”。
- 最终 Bean 的创建仍然发生在 Spring 容器刷新阶段。
AutoConfigurationImportSelector 做什么
@EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector。它的职责可以理解为:
flowchart TD
A["AutoConfigurationImportSelector"] --> B["找自动配置声明文件"]
B --> C["读取候选配置类名"]
C --> D["处理 exclude 排除项"]
D --> E["去重"]
E --> F["返回配置类列表"]
F --> G["Spring 继续解析这些配置类"]它不是直接创建 Bean,而是告诉 Spring:“这些自动配置类也要加入配置类解析流程”。
Boot 2.6、Boot 2.7 和 Boot 3 的声明文件区别
Spring Boot 2.6及更早常见方式
Boot 2 常见自动配置声明位置:
META-INF/spring.factories内容示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.DemoAutoConfigurationSpring Boot 2.7过渡代际
Boot 2.7 已经引入并推荐使用后面的 AutoConfiguration.imports 文件,同时仍兼容通过 spring.factories 的 EnableAutoConfiguration key 注册候选类。它是从旧机制迁向 Boot 3 的关键过渡版本。
Spring Boot 3
Boot 3 自动配置候选声明位置:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容示例:
com.example.demo.DemoAutoConfiguration对比:
| 对比项 | Boot 2.6及更早常见方式 | Boot 2.7 | Boot 3 |
|---|---|---|---|
| 声明文件 | META-INF/spring.factories | 两种方式可过渡,新组件推荐 imports | META-INF/spring/...AutoConfiguration.imports |
| 写法 | key-value 形式 | 兼容旧方式或每行一个类 | 每行一个自动配置类 |
| 面试重点 | 老项目常见 | 新旧机制过渡点 | 旧EnableAutoConfiguration候选注册方式已移除 |
注意:不是看到 spring.factories 就说明项目错了。很多 JDK 8、Spring Boot 2.x 老项目仍然大量使用它;Boot 3 中其他工厂扩展也仍可能使用该文件。这里移除的是通过旧 EnableAutoConfiguration key 注册自动配置候选类的机制,不能扩张成“Boot 3 完全不读 spring.factories”。完整版本迁移见 Java 17+ 与 Spring Boot 3.x。
条件注解为什么重要
自动配置的核心不是“导入配置类”,而是“条件满足才注册 Bean”。
常见条件注解:
| 注解 | 判断条件 | 典型场景 |
|---|---|---|
@ConditionalOnClass | classpath 存在某个类 | 引入 Web 依赖才配置 MVC |
@ConditionalOnMissingClass | classpath 不存在某个类 | 某能力不存在时启用备用方案 |
@ConditionalOnBean | 容器中存在某个 Bean | 有数据源后再配置 JdbcTemplate |
@ConditionalOnMissingBean | 容器中不存在某个 Bean | 用户没自定义时创建默认 Bean |
@ConditionalOnProperty | 配置项满足条件 | 开关控制某功能是否启用 |
@ConditionalOnWebApplication | 当前是 Web 应用 | 只在 Web 项目启用 MVC 配置 |
@ConditionalOnNotWebApplication | 当前不是 Web 应用 | CLI、批处理项目启用另一套配置 |
@ConditionalOnResource | 存在某资源文件 | 按资源决定配置 |
@ConditionalOnExpression | SpEL 表达式成立 | 复杂条件判断,少用 |
条件判断流程:
flowchart TD
A["候选自动配置类"] --> B{"classpath 是否满足"}
B -- "否" --> C["跳过"]
B -- "是" --> D{"配置开关是否满足"}
D -- "否" --> C
D -- "是" --> E{"容器是否缺少用户 Bean"}
E -- "否" --> F["默认 Bean 让位"]
E -- "是" --> G["注册默认 Bean"]条件注解到底什么时候判断
很多人以为条件注解是在 Bean 创建时才判断,这只说对了一部分。自动配置里常见条件大致会出现在两个层级:
| 条件写在哪里 | 判断对象 | 结果 |
|---|---|---|
| 写在自动配置类上 | 整个配置类是否参与解析 | 不匹配时,这个配置类里的 Bean 方法基本都不处理 |
写在 @Bean 方法上 | 这个 BeanDefinition 是否注册 | 不匹配时,只跳过这个 Bean |
示例:
@AutoConfiguration
@ConditionalOnClass(CollectClient.class)
public class CollectClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient() {
return new CollectClient();
}
}执行过程:
flowchart TD
A["读取候选自动配置类"] --> B["判断类级条件"]
B -- "CollectClient 类不存在" --> C["整类跳过"]
B -- "CollectClient 类存在" --> D["解析配置类"]
D --> E["解析 @Bean 方法"]
E --> F["判断方法级条件"]
F -- "容器已有 CollectClient" --> G["跳过默认 Bean"]
F -- "容器没有 CollectClient" --> H["注册默认 BeanDefinition"]为什么要区分类级和方法级?因为类级条件可以更早剪枝,避免加载和解析一整套无用配置;方法级条件更细,可以让同一个自动配置类里的不同 Bean 分别判断。
如果所有条件都写在方法上,某些缺依赖场景可能导致配置类解析时就触发类加载问题;如果所有条件都写在类上,又会丢失局部 Bean 的精细让位能力。公共 starter 里通常两者配合使用。
@ConditionalOnClass 为什么不一定会加载类
@ConditionalOnClass 经常用于判断 classpath 里有没有某个依赖。它要尽量避免一个问题:依赖不存在时,如果直接加载缺失类,会先抛 ClassNotFoundException,条件判断还没来得及优雅跳过。
Spring Boot 会尽量通过元数据读取注解信息,而不是一上来就初始化相关类。可以简单理解为:先读 class 文件上的名字,判断 classpath 中是否存在,再决定配置类是否继续参与。
flowchart TD
A["候选配置类"] --> B["读取注解元数据"]
B --> C["拿到 ConditionalOnClass 的类名"]
C --> D{"classpath 是否存在该类"}
D -- "不存在" --> E["条件不匹配,跳过"]
D -- "存在" --> F["继续解析配置类"]这就是为什么自动配置类里常把第三方依赖判断放到类级别:
@AutoConfiguration
@ConditionalOnClass(name = "com.rabbitmq.client.ConnectionFactory")
public class RabbitClientAutoConfiguration {
}name 写法在某些场景下更稳,因为它避免源码直接引用一个可能不存在的类。公共 starter 面向很多项目,不能假设所有依赖都一定存在。
@ConditionalOnBean 和 @ConditionalOnMissingBean 的时机坑
@ConditionalOnMissingBean 很重要,但也最容易被误解。它判断的是“当前已经注册到容器里的 BeanDefinition 或 Bean”,不是预知未来所有 Bean。
flowchart TD
A["解析自动配置 A"] --> B["判断 MissingBean"]
B --> C{"当前是否已有目标 Bean"}
C -- "没有" --> D["注册默认 Bean"]
C -- "有" --> E["跳过默认 Bean"]
D --> F["后面又解析用户配置"]如果顺序设计不好,可能出现默认 Bean 先注册,用户 Bean 后注册,最后同类型 Bean 冲突。Spring Boot 为了减少这种问题,会尽量让用户配置优先于自动配置,并通过自动配置排序和条件评估规则控制默认让位。但自定义 starter 写得不规范时,仍然可能踩坑。
商业项目写自动配置时建议:
- 默认 Bean 一定加
@ConditionalOnMissingBean。 - 自动配置类用
@AutoConfiguration,不要让业务通过组件扫描扫进来。 - 不要在自动配置里过早
getBean(),否则可能提前实例化业务 Bean。 - 多个自动配置之间有依赖时,用
@AutoConfigureAfter或@AutoConfigureBefore表达大致顺序。 - 排查冲突时看
/actuator/conditions和 Bean 定义来源。
条件注解判断依赖哪些数据
条件注解不是只看一个地方,而是综合多个上下文。
| 条件类型 | 主要依赖 | 例子 |
|---|---|---|
| Class 条件 | classpath | 是否引入 spring-webmvc、Redis、RabbitMQ |
| Bean 条件 | BeanFactory 中已有 BeanDefinition/Bean | 是否已有 DataSource、RedisTemplate |
| Property 条件 | Environment 中的最终配置值 | xxx.enabled=true |
| Resource 条件 | classpath 或文件资源 | 是否存在某个配置文件 |
| Web 条件 | ApplicationContext 类型和 WebApplicationType | Servlet、Reactive、非 Web |
这解释了为什么配置体系必须在自动配置之前准备好:@ConditionalOnProperty 要读 Environment,Profile、环境变量、命令行参数、配置中心都会影响最终条件结果。
flowchart TD
A["准备 Environment"] --> B["加载配置源和 Profile"]
B --> C["解析启动类和自动配置"]
C --> D["@ConditionalOnProperty 读取最终配置"]
D --> E{"配置是否满足"}
E -- "满足" --> F["注册 BeanDefinition"]
E -- "不满足" --> G["跳过"]如果你明明在 application.yml 写了开关,但自动配置仍不生效,要怀疑:
- 当前激活的 Profile 不是你以为的那个。
- 环境变量或命令行参数覆盖了文件配置。
- 配置中心下发了不同值。
- 属性名写错,宽松绑定没有匹配上。
- 自动配置类读取的是另一个 prefix。
自定义 Condition Demo
大多数业务不需要自定义 Condition,用 Boot 内置条件注解就够了。但理解自定义条件有助于看懂自动配置底层。
需求:只有当配置项 collect.mode=hospital 时才注册医院采集客户端。
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class HospitalModeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String mode = context.getEnvironment().getProperty("collect.mode");
return "hospital".equalsIgnoreCase(mode);
}
}使用:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
public class CollectClientAutoConfiguration {
@Bean
@Conditional(HospitalModeCondition.class)
public HospitalCollectClient hospitalCollectClient() {
return new HospitalCollectClient();
}
}流程:
flowchart TD
A["解析 @Bean 方法"] --> B["发现 @Conditional"]
B --> C["执行 Condition.matches"]
C --> D["从 Environment 读取 collect.mode"]
D --> E{"是否 hospital"}
E -- "是" --> F["注册 HospitalCollectClient"]
E -- "否" --> G["跳过该 Bean"]为什么不建议滥用自定义 Condition?
| 风险 | 说明 |
|---|---|
| 判断逻辑分散 | 排查时要读代码才知道为什么不生效 |
| 可能过早访问 Bean | 条件阶段不应该触发业务 Bean 创建 |
| 可观测性差 | 不如标准条件在 conditions 报告里直观 |
| 维护成本高 | 新人不容易理解默认装配规则 |
优先级建议:能用 @ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean 解决,就不要自定义 Condition。
为什么用户 Bean 会覆盖默认 Bean
自动配置类里经常写:
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DemoAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MessageService messageService() {
return new DefaultMessageService();
}
}意思是:容器里没有 MessageService 时,才创建默认实现。
如果业务项目自己定义:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BizMessageConfig {
@Bean
public MessageService messageService() {
return new DbMessageService();
}
}那么自动配置中的默认 MessageService 就不会创建。
这就是 Spring Boot 的默认让位原则:
框架提供默认值,业务需要时可以接管。
如果没有这个原则,Starter 一旦引入就会强行创建 Bean,业务项目很难扩展。
自动配置和 BeanDefinition 的关系
自动配置类本质上仍然是配置类。配置类里的 @Bean 方法最终会被解析成 BeanDefinition。
flowchart TD
A["自动配置类"] --> B["@Bean 方法"]
B --> C["解析为 BeanDefinition"]
C --> D["放入 BeanDefinitionRegistry"]
D --> E["refresh 创建单例 Bean"]
E --> F["加入单例池"]所以自动配置不是绕过 Spring IOC 的特殊通道,而是把更多配置类自动导入到 Spring IOC 流程里。
想真正理解自动配置,必须同时理解:
自动配置顺序
有些自动配置之间有先后关系。例如需要先有数据源,再配置 JdbcTemplate。
常见控制方式:
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DemoJdbcAutoConfiguration {
}也可以使用:
| 注解 | 含义 |
|---|---|
@AutoConfigureBefore | 当前自动配置在某配置之前 |
@AutoConfigureAfter | 当前自动配置在某配置之后 |
@AutoConfigureOrder | 设置自动配置顺序 |
但不要滥用顺序控制。如果自动配置之间强依赖很多,说明 Starter 设计可能太重。
自动配置顺序不等于 Bean 创建顺序
这里很容易混淆。@AutoConfigureBefore、@AutoConfigureAfter 控制的是自动配置类被处理的大致先后,主要影响条件判断和 BeanDefinition 注册;它不等于最终 Bean 一定按这个顺序实例化。
最终 Bean 创建还会受到这些因素影响:
| 因素 | 说明 |
|---|---|
| 依赖关系 | A 构造器依赖 B,通常会先创建 B |
@DependsOn | 显式声明某 Bean 先创建 |
| 懒加载 | @Lazy 或全局懒加载会推迟创建 |
| 条件注解 | 条件不满足时 BeanDefinition 根本不会注册 |
| BeanPostProcessor | 初始化前后可能包装成代理对象 |
可以这样理解:
flowchart TD
A["自动配置排序"] --> B["决定配置类解析顺序"]
B --> C["注册 BeanDefinition"]
C --> D["refresh 创建 Bean"]
D --> E["按依赖关系和生命周期实例化"]面试里如果被追问“自动配置排序是不是 Bean 创建顺序”,不能简单回答“是”。更准确的是:排序影响自动配置类导入和 BeanDefinition 注册时机,但 Bean 的真实创建由 Spring IOC 生命周期、依赖关系、懒加载和后置处理器共同决定。
自动配置为什么要排序
排序存在的原因不是为了让代码“看起来有先后”,而是为了让条件判断有稳定依据。
例如 JdbcTemplateAutoConfiguration 通常依赖 DataSource。如果数据源相关配置还没有处理,JdbcTemplate 的条件判断就可能没有依据。
flowchart TD
A["DataSourceAutoConfiguration"] --> B["注册 DataSource 定义"]
B --> C["JdbcTemplateAutoConfiguration"]
C --> D{"容器中是否有 DataSource"}
D -- "有" --> E["注册 JdbcTemplate"]
D -- "没有" --> F["跳过"]这就是为什么框架自动配置会声明 before/after。业务自定义 Starter 也可以声明,但要克制:如果两个 Starter 必须精确顺序才能启动,通常意味着边界没有拆清。
自动配置排除
有时你不希望某个自动配置生效,可以排除:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {
}也可以配置:
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration常见场景:
| 场景 | 为什么排除 |
|---|---|
| 项目暂时不连数据库 | 避免 DataSource 自动配置报错 |
| 使用自研数据源接入方式 | 避免默认数据源配置干扰 |
| 测试环境不启动某些组件 | 缩短测试启动时间 |
排除不是常规手段。优先通过配置开关、@ConditionalOnProperty、自定义 Bean 让位来控制。
自定义自动配置 Demo
下面写一个“采集客户端 Starter”的最小自动配置。
1. 属性类
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "collect.client")
public class CollectClientProperties {
private String baseUrl;
private int connectTimeout = 3000;
private int readTimeout = 5000;
private boolean enabled = true;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}2. 客户端类
public class CollectClient {
private final String baseUrl;
private final int connectTimeout;
private final int readTimeout;
public CollectClient(String baseUrl, int connectTimeout, int readTimeout) {
this.baseUrl = baseUrl;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
}
public String fetch(String apiPath) {
return "request " + baseUrl + apiPath
+ ", connectTimeout=" + connectTimeout
+ ", readTimeout=" + readTimeout;
}
}3. 自动配置类
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(CollectClient.class)
@EnableConfigurationProperties(CollectClientProperties.class)
@ConditionalOnProperty(prefix = "collect.client", name = "enabled",
havingValue = "true", matchIfMissing = true)
public class CollectClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient(CollectClientProperties properties) {
return new CollectClient(
properties.getBaseUrl(),
properties.getConnectTimeout(),
properties.getReadTimeout()
);
}
}4. Boot 3 声明文件
文件路径:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件内容:
com.example.collect.CollectClientAutoConfiguration5. 业务项目配置
collect:
client:
enabled: true
base-url: https://hospital.example.com
connect-timeout: 2000
read-timeout: 80006. 业务项目使用
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CollectController {
private final CollectClient collectClient;
public CollectController(CollectClient collectClient) {
this.collectClient = collectClient;
}
@GetMapping("/collect/test")
public String test() {
return collectClient.fetch("/api/patient");
}
}如果业务项目想完全接管默认客户端:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomCollectClientConfig {
@Bean
public CollectClient collectClient() {
return new CollectClient("https://custom.example.com", 1000, 1000);
}
}因为自动配置中写了 @ConditionalOnMissingBean,所以默认客户端会让位。
商业项目怎么用
医疗数据采集平台里,适合做 Starter 和自动配置的能力包括:
| 能力 | 为什么适合自动配置 |
|---|---|
| 医院接口客户端 | 多个服务都要调用,超时、重试、日志要统一 |
| 采集线程池 | 参数来自配置,默认值统一 |
| 统一审计日志 | 每个服务都需要,业务只配置开关 |
| Redis Key 规范工具 | 多服务共享 key 前缀和序列化规则 |
| 脱敏组件 | 返回患者信息前统一处理 |
| Actuator 自定义健康检查 | 统一暴露外部依赖健康状态 |
不适合放进自动配置的内容:
| 内容 | 原因 |
|---|---|
| 具体医院的业务规则 | 会污染所有服务 |
| 大量数据库业务 SQL | Starter 应该提供基础设施,不写领域逻辑 |
| 启动时强制远程连接 | 会拖慢启动,甚至导致服务无法启动 |
| 强制覆盖用户 Bean | 破坏扩展性 |
线上排查:自动配置为什么没生效
1. 开启 debug 报告
debug: true启动日志中会出现条件评估报告,包含:
| 区域 | 含义 |
|---|---|
| Positive matches | 条件匹配,自动配置生效 |
| Negative matches | 条件不匹配,自动配置未生效 |
| Exclusions | 被排除的自动配置 |
| Unconditional classes | 无条件配置类 |
2. 使用 Actuator conditions
配置:
management:
endpoints:
web:
exposure:
include: conditions访问:
curl http://127.0.0.1:8080/actuator/conditions3. 排查顺序
flowchart TD
A["Bean 没自动配置出来"] --> B["看 conditions 报告"]
B --> C{"自动配置类是否在候选列表"}
C -- "否" --> D["检查 imports 或 spring.factories"]
C -- "是" --> E{"条件是否匹配"}
E -- "否" --> F["检查依赖、配置项、Web 环境"]
E -- "是" --> G{"是否被用户 Bean 覆盖"}
G -- "是" --> H["检查 @ConditionalOnMissingBean"]
G -- "否" --> I["检查 Bean 创建异常和日志"]常见问题:
| 问题 | 可能原因 | 处理 |
|---|---|---|
| 自动配置类完全没加载 | 声明文件路径错、类名错、依赖没引入 | 检查 jar 包和声明文件 |
| 条件不匹配 | 少依赖、配置开关关闭、非 Web 环境 | 看 negative matches |
| Bean 没创建 | 已经有同类型 Bean | 看 @ConditionalOnMissingBean |
| Bean 创建失败 | 构造方法里连外部系统失败 | 延迟连接,设置超时 |
| 本地正常线上不正常 | Profile、环境变量、配置中心不同 | 打印有效配置来源 |
4. conditions 报告怎么看
不要只看“Positive matches 很多”就以为没问题。排查某个 Bean 没出来时,要围绕目标自动配置类看。
假设 CollectClient 没创建,排查证据链应该是:
| 问题 | 去哪里看 | 判断 |
|---|---|---|
| 自动配置类有没有被发现 | conditions 报告里搜索 CollectClientAutoConfiguration | 没出现说明 imports/spring.factories 或依赖有问题 |
| 类级条件是否匹配 | Positive/Negative matches | @ConditionalOnClass、Web 类型、资源是否满足 |
| 配置开关是否正确 | /actuator/env 或启动日志 | collect.client.enabled 最终值是什么 |
| 默认 Bean 是否让位 | conditions 中 @ConditionalOnMissingBean 结果 | 是否已有用户自定义 CollectClient |
| Bean 创建是否异常 | 启动异常栈和日志 | 构造方法、配置绑定、外部连接是否失败 |
| 配置有没有绑定成功 | /actuator/configprops | CollectClientProperties 是否有期望值 |
排查流程:
flowchart TD
A["目标 Bean 不存在"] --> B["搜索 conditions 中的自动配置类"]
B --> C{"自动配置类是否出现"}
C -- "否" --> D["检查 starter 依赖和声明文件"]
C -- "是" --> E["看 Negative matches 原因"]
E --> F{"是否缺 classpath 依赖"}
F -- "是" --> G["补依赖或修 starter"]
F -- "否" --> H{"是否配置项不满足"}
H -- "是" --> I["看 env 最终配置值"]
H -- "否" --> J{"是否 MissingBean 让位"}
J -- "是" --> K["找到已有用户 Bean"]
J -- "否" --> L["看 Bean 创建异常和 configprops"]5. 常见自动配置失败案例
案例一:声明文件路径写错
Boot 3 推荐路径是:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports如果写成下面这样,Boot 可能找不到:
META-INF/AutoConfiguration.imports表现:
- conditions 报告里完全搜不到自动配置类。
- 业务项目引入 starter,但没有任何默认 Bean。
- 没有明显异常,只是“不生效”。
修复:检查 jar 包最终内容,而不是只看源码目录。可以用解压工具或构建产物确认声明文件确实被打进 jar。
案例二:@ConditionalOnProperty 默认值理解错
@ConditionalOnProperty(prefix = "collect.client", name = "enabled", havingValue = "true")这表示必须明确配置:
collect:
client:
enabled: true如果希望“不配置时默认开启”,要写:
@ConditionalOnProperty(
prefix = "collect.client",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)否则线上删掉配置后,默认 Bean 可能突然不创建。
案例三:@ConditionalOnMissingBean 被业务 Bean 顶掉
业务项目里可能有:
@Bean
public CollectClient collectClient() {
return new CollectClient("http://local", 1000, 1000);
}自动配置里:
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient(CollectClientProperties properties) {
return new CollectClient(properties.getBaseUrl(), 3000, 5000);
}最终默认 Bean 不会创建。这不是自动配置失效,而是让位机制生效。排查时要找“谁已经提供了同类型 Bean”。
案例四:构造方法里连接外部系统
错误示例:
public CollectClient(String baseUrl) {
this.connection = connect(baseUrl); // 启动时立刻远程连接
}如果医院接口、Redis、MQ 或配置中心短暂不可用,应用启动就失败。公共 starter 不应该在 Bean 构造阶段做不可控远程连接。
更稳的做法:
- 构造阶段只保存配置。
- 真正请求时再建立连接,或使用连接池懒加载。
- 健康检查通过
HealthIndicator暴露依赖状态。 - 对必须启动检查的组件设置明确超时和失败提示。
常见误区
| 误区 | 为什么错 | 正确理解 |
|---|---|---|
| 自动配置就是组件扫描 | 自动配置读取候选配置类,组件扫描扫描业务包 | 二者都注册 Bean,但来源不同 |
| 引入 Starter 就一定创建 Bean | 条件不满足会跳过 | Starter 只是提供候选能力 |
@EnableAutoConfiguration 直接创建 Bean | 它导入选择器,选择器导入配置类 | Bean 创建仍由 IOC 完成 |
Boot 3 不能用 spring.factories | 老机制仍可能存在兼容场景 | 面试要区分推荐写法和历史写法 |
| 默认配置不能改 | 默认 Bean 通常会让位 | 自定义 Bean 或配置项即可接管 |
面试标准回答
可以这样答:
Spring Boot 自动配置的核心是 @EnableAutoConfiguration。它通过 @Import 导入 AutoConfigurationImportSelector,启动时读取自动配置候选类。Boot 2.6及更早常见读取 META-INF/spring.factories,Boot 2.7 已支持并推荐 AutoConfiguration.imports,Boot 3 使用该 imports 机制。候选配置类不会全部生效,而是通过 @ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean、@ConditionalOnWebApplication 等条件注解决定是否注册默认 Bean。
自动配置不是绕过 Spring 的魔法,自动配置类里的 @Bean 最终仍然会被解析成 BeanDefinition,并在 Spring 容器 refresh 阶段创建 Bean。很多默认 Bean 使用 @ConditionalOnMissingBean,所以业务项目自己定义 Bean 后,Boot 默认实现会让位。排查自动配置问题时,我会看 conditions 报告,确认自动配置类是否被加载、条件是否匹配、是否被 exclude、是否已经存在用户自定义 Bean。追问:
| 追问 | 回答要点 |
|---|---|
| Starter 和自动配置什么关系 | Starter 聚合依赖,autoconfigure 提供自动配置,声明文件让 Boot 找到配置类 |
| 为什么引入 Web Starter 就有 Tomcat | Starter 引入 Tomcat 和 MVC 依赖,Web 自动配置条件满足后创建 Web 相关 Bean |
@ConditionalOnMissingBean 为什么重要 | 默认让位,避免框架强行覆盖用户配置 |
| Boot 2 和 Boot 3 自动配置文件区别 | Boot 2.6及更早常见 spring.factories,Boot 2.7支持imports,Boot 3使用 AutoConfiguration.imports |
| 怎么排查没生效 | 开 debug=true 或看 /actuator/conditions |
| 自动配置和 IOC 什么关系 | 自动配置类最终仍被解析为 BeanDefinition,交给 IOC 创建 Bean |
关联知识点
- Spring Boot 自动配置与扩展点全过程:把自动配置、Starter、生命周期扩展点和生产排查串起来。
- Spring Boot 启动原理:理解自动配置处于启动流程哪一步。
- Spring Boot Starter:理解 Starter 如何承载自动配置。
- Spring Boot 扩展点:理解 EnvironmentPostProcessor、ApplicationRunner、FailureAnalyzer 等扩展。
- Spring IOC:理解 BeanDefinition 和 BeanFactory。
- Spring 核心扩展点:理解 BeanFactoryPostProcessor、BeanPostProcessor 等底层扩展。
本章小结
Spring Boot 自动配置的核心不是“省配置”,而是把通用基础设施封装成一套可发现、可判断、可让位、可排查的默认装配机制。它依赖 Spring IOC 完成 Bean 注册和创建,依赖条件注解决定是否生效,依赖 Starter 传播依赖和配置入口。真正掌握自动配置后,你就能看懂 Starter 为什么开箱即用,也能定位为什么某个默认 Bean 没出来。
