Skip to content

Spring Boot 自动配置原理

Spring Boot 自动配置不是“魔法”,它的本质是:

Spring Boot 在启动时准备一批候选配置类,再根据 classpath、配置项、容器中已有 Bean、Web 环境等条件判断,决定哪些默认 Bean 应该注册到 Spring 容器里。

如果你只会说“约定优于配置”,面试只能答到表面。真正要理解自动配置,必须把这条链路讲清楚:

text
@SpringBootApplication
-> @EnableAutoConfiguration
-> AutoConfigurationImportSelector
-> 读取自动配置候选类
-> 条件注解筛选
-> 注册 BeanDefinition
-> refresh 阶段创建 Bean

学习目标

学完本页,你应该能回答:

  1. @SpringBootApplication 为什么能启动自动配置。
  2. @EnableAutoConfiguration 自己到底做了什么。
  3. Boot 2.6及更早、Boot 2.7和Boot 3的自动配置候选声明有什么区别。
  4. 条件注解为什么是自动配置的核心。
  5. 为什么用户自定义 Bean 后,Boot 默认 Bean 会让位。
  6. 自动配置为什么最终仍然要回到 Spring IOC 和 Bean 生命周期。
  7. Starter 和自动配置是什么关系。
  8. 线上怎么排查某个自动配置为什么没生效。

如果你已经知道基础概念,想把自动配置、Starter、扩展点和生产排查串成一条完整链路,继续看 Spring Boot 自动配置与扩展点全过程

从一个现象开始

创建一个最小 Web 项目:

java
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 猜到了你的想法,而是因为:

  1. 你引入了 spring-boot-starter-web
  2. classpath 里出现了 Spring MVC、Jackson、Tomcat 等类。
  3. 自动配置类判断条件成立。
  4. Spring Boot 注册了一批默认 Bean。
  5. 如果你自己定义 Bean,默认 Bean 会让位。

@SpringBootApplication 拆开看

@SpringBootApplication 是组合注解,核心可以理解成三部分:

注解作用如果没有会怎样
@SpringBootConfiguration表示这是一个 Spring 配置类启动类不再作为配置类参与解析
@ComponentScan扫描当前包及子包组件Controller、Service 等不会自动注册
@EnableAutoConfiguration开启自动配置Web、数据源、Redis、MVC 等默认能力不会自动装配

流程图:

mermaid
flowchart TD
    A["启动类标注 @SpringBootApplication"] --> B["@SpringBootConfiguration"]
    A --> C["@ComponentScan"]
    A --> D["@EnableAutoConfiguration"]
    B --> E["启动类作为配置类"]
    C --> F["扫描业务组件"]
    D --> G["导入自动配置候选类"]

很多人把 @SpringBootApplication 等同于“启动注解”,这是不准确的。它真正做的是把配置类、组件扫描和自动配置三件事放在一起。

自动配置完整流程

自动配置不是在运行时到处扫描 jar 包,而是读取约定位置的自动配置清单。

mermaid
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"]

关键点:

  1. 自动配置类先作为候选名单被导入。
  2. 并不是所有候选配置都会生效。
  3. 条件注解决定“是否装配”。
  4. 最终 Bean 的创建仍然发生在 Spring 容器刷新阶段。

AutoConfigurationImportSelector 做什么

@EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector。它的职责可以理解为:

mermaid
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 常见自动配置声明位置:

text
META-INF/spring.factories

内容示例:

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.DemoAutoConfiguration

Spring Boot 2.7过渡代际

Boot 2.7 已经引入并推荐使用后面的 AutoConfiguration.imports 文件,同时仍兼容通过 spring.factoriesEnableAutoConfiguration key 注册候选类。它是从旧机制迁向 Boot 3 的关键过渡版本。

Spring Boot 3

Boot 3 自动配置候选声明位置:

text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容示例:

text
com.example.demo.DemoAutoConfiguration

对比:

对比项Boot 2.6及更早常见方式Boot 2.7Boot 3
声明文件META-INF/spring.factories两种方式可过渡,新组件推荐 importsMETA-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”。

常见条件注解:

注解判断条件典型场景
@ConditionalOnClassclasspath 存在某个类引入 Web 依赖才配置 MVC
@ConditionalOnMissingClassclasspath 不存在某个类某能力不存在时启用备用方案
@ConditionalOnBean容器中存在某个 Bean有数据源后再配置 JdbcTemplate
@ConditionalOnMissingBean容器中不存在某个 Bean用户没自定义时创建默认 Bean
@ConditionalOnProperty配置项满足条件开关控制某功能是否启用
@ConditionalOnWebApplication当前是 Web 应用只在 Web 项目启用 MVC 配置
@ConditionalOnNotWebApplication当前不是 Web 应用CLI、批处理项目启用另一套配置
@ConditionalOnResource存在某资源文件按资源决定配置
@ConditionalOnExpressionSpEL 表达式成立复杂条件判断,少用

条件判断流程:

mermaid
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

示例:

java
@AutoConfiguration
@ConditionalOnClass(CollectClient.class)
public class CollectClientAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CollectClient collectClient() {
        return new CollectClient();
    }
}

执行过程:

mermaid
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 中是否存在,再决定配置类是否继续参与。

mermaid
flowchart TD
    A["候选配置类"] --> B["读取注解元数据"]
    B --> C["拿到 ConditionalOnClass 的类名"]
    C --> D{"classpath 是否存在该类"}
    D -- "不存在" --> E["条件不匹配,跳过"]
    D -- "存在" --> F["继续解析配置类"]

这就是为什么自动配置类里常把第三方依赖判断放到类级别:

java
@AutoConfiguration
@ConditionalOnClass(name = "com.rabbitmq.client.ConnectionFactory")
public class RabbitClientAutoConfiguration {
}

name 写法在某些场景下更稳,因为它避免源码直接引用一个可能不存在的类。公共 starter 面向很多项目,不能假设所有依赖都一定存在。

@ConditionalOnBean@ConditionalOnMissingBean 的时机坑

@ConditionalOnMissingBean 很重要,但也最容易被误解。它判断的是“当前已经注册到容器里的 BeanDefinition 或 Bean”,不是预知未来所有 Bean。

mermaid
flowchart TD
    A["解析自动配置 A"] --> B["判断 MissingBean"]
    B --> C{"当前是否已有目标 Bean"}
    C -- "没有" --> D["注册默认 Bean"]
    C -- "有" --> E["跳过默认 Bean"]
    D --> F["后面又解析用户配置"]

如果顺序设计不好,可能出现默认 Bean 先注册,用户 Bean 后注册,最后同类型 Bean 冲突。Spring Boot 为了减少这种问题,会尽量让用户配置优先于自动配置,并通过自动配置排序和条件评估规则控制默认让位。但自定义 starter 写得不规范时,仍然可能踩坑。

商业项目写自动配置时建议:

  1. 默认 Bean 一定加 @ConditionalOnMissingBean
  2. 自动配置类用 @AutoConfiguration,不要让业务通过组件扫描扫进来。
  3. 不要在自动配置里过早 getBean(),否则可能提前实例化业务 Bean。
  4. 多个自动配置之间有依赖时,用 @AutoConfigureAfter@AutoConfigureBefore 表达大致顺序。
  5. 排查冲突时看 /actuator/conditions 和 Bean 定义来源。

条件注解判断依赖哪些数据

条件注解不是只看一个地方,而是综合多个上下文。

条件类型主要依赖例子
Class 条件classpath是否引入 spring-webmvc、Redis、RabbitMQ
Bean 条件BeanFactory 中已有 BeanDefinition/Bean是否已有 DataSourceRedisTemplate
Property 条件Environment 中的最终配置值xxx.enabled=true
Resource 条件classpath 或文件资源是否存在某个配置文件
Web 条件ApplicationContext 类型和 WebApplicationTypeServlet、Reactive、非 Web

这解释了为什么配置体系必须在自动配置之前准备好:@ConditionalOnProperty 要读 Environment,Profile、环境变量、命令行参数、配置中心都会影响最终条件结果。

mermaid
flowchart TD
    A["准备 Environment"] --> B["加载配置源和 Profile"]
    B --> C["解析启动类和自动配置"]
    C --> D["@ConditionalOnProperty 读取最终配置"]
    D --> E{"配置是否满足"}
    E -- "满足" --> F["注册 BeanDefinition"]
    E -- "不满足" --> G["跳过"]

如果你明明在 application.yml 写了开关,但自动配置仍不生效,要怀疑:

  1. 当前激活的 Profile 不是你以为的那个。
  2. 环境变量或命令行参数覆盖了文件配置。
  3. 配置中心下发了不同值。
  4. 属性名写错,宽松绑定没有匹配上。
  5. 自动配置类读取的是另一个 prefix。

自定义 Condition Demo

大多数业务不需要自定义 Condition,用 Boot 内置条件注解就够了。但理解自定义条件有助于看懂自动配置底层。

需求:只有当配置项 collect.mode=hospital 时才注册医院采集客户端。

java
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);
    }
}

使用:

java
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();
    }
}

流程:

mermaid
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

自动配置类里经常写:

java
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 时,才创建默认实现。

如果业务项目自己定义:

java
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。

mermaid
flowchart TD
    A["自动配置类"] --> B["@Bean 方法"]
    B --> C["解析为 BeanDefinition"]
    C --> D["放入 BeanDefinitionRegistry"]
    D --> E["refresh 创建单例 Bean"]
    E --> F["加入单例池"]

所以自动配置不是绕过 Spring IOC 的特殊通道,而是把更多配置类自动导入到 Spring IOC 流程里。

想真正理解自动配置,必须同时理解:

  1. Spring IOC
  2. Bean 生命周期
  3. Spring 扩展点

自动配置顺序

有些自动配置之间有先后关系。例如需要先有数据源,再配置 JdbcTemplate。

常见控制方式:

java
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初始化前后可能包装成代理对象

可以这样理解:

mermaid
flowchart TD
    A["自动配置排序"] --> B["决定配置类解析顺序"]
    B --> C["注册 BeanDefinition"]
    C --> D["refresh 创建 Bean"]
    D --> E["按依赖关系和生命周期实例化"]

面试里如果被追问“自动配置排序是不是 Bean 创建顺序”,不能简单回答“是”。更准确的是:排序影响自动配置类导入和 BeanDefinition 注册时机,但 Bean 的真实创建由 Spring IOC 生命周期、依赖关系、懒加载和后置处理器共同决定。

自动配置为什么要排序

排序存在的原因不是为了让代码“看起来有先后”,而是为了让条件判断有稳定依据。

例如 JdbcTemplateAutoConfiguration 通常依赖 DataSource。如果数据源相关配置还没有处理,JdbcTemplate 的条件判断就可能没有依据。

mermaid
flowchart TD
    A["DataSourceAutoConfiguration"] --> B["注册 DataSource 定义"]
    B --> C["JdbcTemplateAutoConfiguration"]
    C --> D{"容器中是否有 DataSource"}
    D -- "有" --> E["注册 JdbcTemplate"]
    D -- "没有" --> F["跳过"]

这就是为什么框架自动配置会声明 before/after。业务自定义 Starter 也可以声明,但要克制:如果两个 Starter 必须精确顺序才能启动,通常意味着边界没有拆清。

自动配置排除

有时你不希望某个自动配置生效,可以排除:

java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {
}

也可以配置:

yaml
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

常见场景:

场景为什么排除
项目暂时不连数据库避免 DataSource 自动配置报错
使用自研数据源接入方式避免默认数据源配置干扰
测试环境不启动某些组件缩短测试启动时间

排除不是常规手段。优先通过配置开关、@ConditionalOnProperty、自定义 Bean 让位来控制。

自定义自动配置 Demo

下面写一个“采集客户端 Starter”的最小自动配置。

1. 属性类

java
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. 客户端类

java
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. 自动配置类

java
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 声明文件

文件路径:

text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容:

text
com.example.collect.CollectClientAutoConfiguration

5. 业务项目配置

yaml
collect:
  client:
    enabled: true
    base-url: https://hospital.example.com
    connect-timeout: 2000
    read-timeout: 8000

6. 业务项目使用

java
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");
    }
}

如果业务项目想完全接管默认客户端:

java
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 自定义健康检查统一暴露外部依赖健康状态

不适合放进自动配置的内容:

内容原因
具体医院的业务规则会污染所有服务
大量数据库业务 SQLStarter 应该提供基础设施,不写领域逻辑
启动时强制远程连接会拖慢启动,甚至导致服务无法启动
强制覆盖用户 Bean破坏扩展性

线上排查:自动配置为什么没生效

1. 开启 debug 报告

yaml
debug: true

启动日志中会出现条件评估报告,包含:

区域含义
Positive matches条件匹配,自动配置生效
Negative matches条件不匹配,自动配置未生效
Exclusions被排除的自动配置
Unconditional classes无条件配置类

2. 使用 Actuator conditions

配置:

yaml
management:
  endpoints:
    web:
      exposure:
        include: conditions

访问:

bash
curl http://127.0.0.1:8080/actuator/conditions

3. 排查顺序

mermaid
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/configpropsCollectClientProperties 是否有期望值

排查流程:

mermaid
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 推荐路径是:

text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

如果写成下面这样,Boot 可能找不到:

text
META-INF/AutoConfiguration.imports

表现:

  1. conditions 报告里完全搜不到自动配置类。
  2. 业务项目引入 starter,但没有任何默认 Bean。
  3. 没有明显异常,只是“不生效”。

修复:检查 jar 包最终内容,而不是只看源码目录。可以用解压工具或构建产物确认声明文件确实被打进 jar。

案例二:@ConditionalOnProperty 默认值理解错

java
@ConditionalOnProperty(prefix = "collect.client", name = "enabled", havingValue = "true")

这表示必须明确配置:

yaml
collect:
  client:
    enabled: true

如果希望“不配置时默认开启”,要写:

java
@ConditionalOnProperty(
    prefix = "collect.client",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)

否则线上删掉配置后,默认 Bean 可能突然不创建。

案例三:@ConditionalOnMissingBean 被业务 Bean 顶掉

业务项目里可能有:

java
@Bean
public CollectClient collectClient() {
    return new CollectClient("http://local", 1000, 1000);
}

自动配置里:

java
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient(CollectClientProperties properties) {
    return new CollectClient(properties.getBaseUrl(), 3000, 5000);
}

最终默认 Bean 不会创建。这不是自动配置失效,而是让位机制生效。排查时要找“谁已经提供了同类型 Bean”。

案例四:构造方法里连接外部系统

错误示例:

java
public CollectClient(String baseUrl) {
    this.connection = connect(baseUrl); // 启动时立刻远程连接
}

如果医院接口、Redis、MQ 或配置中心短暂不可用,应用启动就失败。公共 starter 不应该在 Bean 构造阶段做不可控远程连接。

更稳的做法:

  1. 构造阶段只保存配置。
  2. 真正请求时再建立连接,或使用连接池懒加载。
  3. 健康检查通过 HealthIndicator 暴露依赖状态。
  4. 对必须启动检查的组件设置明确超时和失败提示。

常见误区

误区为什么错正确理解
自动配置就是组件扫描自动配置读取候选配置类,组件扫描扫描业务包二者都注册 Bean,但来源不同
引入 Starter 就一定创建 Bean条件不满足会跳过Starter 只是提供候选能力
@EnableAutoConfiguration 直接创建 Bean它导入选择器,选择器导入配置类Bean 创建仍由 IOC 完成
Boot 3 不能用 spring.factories老机制仍可能存在兼容场景面试要区分推荐写法和历史写法
默认配置不能改默认 Bean 通常会让位自定义 Bean 或配置项即可接管

面试标准回答

可以这样答:

text
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 就有 TomcatStarter 引入 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 自动配置的核心不是“省配置”,而是把通用基础设施封装成一套可发现、可判断、可让位、可排查的默认装配机制。它依赖 Spring IOC 完成 Bean 注册和创建,依赖条件注解决定是否生效,依赖 Starter 传播依赖和配置入口。真正掌握自动配置后,你就能看懂 Starter 为什么开箱即用,也能定位为什么某个默认 Bean 没出来。