跳到正文
Joeplover
学习笔记·2026-07-30·约 12 分钟阅读

Spring Boot 核心注解详解:从配置绑定到自动配置

从 @ConditionalOnClass、@ConditionalOnProperty 到 @ConfigurationProperties、@EnableConfigurationProperties 和 @ConfigurationPropertiesScan,一次理清 Spring Boot 配置与自动配置的职责边界。

代码编辑器中的 Spring Boot 配置学习内容

Spring Boot 核心注解详解:从配置绑定到自动配置

在 Spring Boot 项目里,我们经常看到一组看起来很像的注解:@ConditionalOnClass、@ConditionalOnProperty、@ConfigurationProperties、@EnableConfigurationProperties、@ConfigurationPropertiesScan,还有 @ConditionalOnMissingBean。

它们都出现在配置类附近,但解决的问题完全不同。最容易混淆的地方是:有的注解负责判断“要不要加载”,有的负责把配置文件变成 Java 对象,还有的负责把这个对象注册进 Spring 容器。

这篇文章不从注解定义开始背,而是从一个实际问题开始:如果我要写一个可配置、可开关、可复用的 RabbitMQ Starter,Spring Boot 到底会怎样把配置文件变成 Bean?

一、先记住三种不同职责

先把所有注解分成三组:

职责代表注解解决的问题
条件判断@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean什么时候应该加载
配置绑定@ConfigurationProperties配置文件如何进入 Java 对象
Bean 注册@Component、@EnableConfigurationProperties、@ConfigurationPropertiesScan这个对象如何进入 Spring 容器

可以先用一句话建立整体模型:

配置绑定负责把数据装进对象,Bean 注册负责让容器管理对象,条件注解负责决定对象是否应该出现。

如果把这三件事混成一件事,就会出现很多错误理解。例如,@ConfigurationProperties 只说明“这个类按照某个前缀绑定配置”,它本身并不等于 @Component,也不自动保证这个类已经是 Bean。

二、@ConditionalOnClass:类路径有依赖才加载

如果没有这个条件会怎样?

假设一个公共 Starter 里直接引用 RabbitTemplate:

@Configuration
public class MqAutoConfiguration {

    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate();
    }
}

如果使用者没有引入 RabbitMQ 依赖,自动配置类仍然可能被解析。此时 Spring 在解析配置类或创建 Bean 时可能找不到相关类型,应用启动就会失败。

@ConditionalOnClass 的意义,就是让自动配置先确认依赖是否存在:

@Configuration
@ConditionalOnClass(RabbitTemplate.class)
public class MqAutoConfiguration {
    // 只有 RabbitTemplate 存在时,这个配置类才参与配置
}

它检查的是当前应用的 Classpath,也就是应用运行时可以加载到的类集合。

典型用途

  • Starter 的可选依赖
  • 数据库、缓存、消息组件的自动配置
  • 实现模块化和可插拔能力
  • 防止使用者没有引入依赖时启动失败

与它相反的是 @ConditionalOnMissingClass:当指定类不存在时才匹配。

一个重要的写法边界

如果条件类本身可能不存在,自动配置通常应该把相关条件放在独立的配置类上,并避免在不满足条件时过早解析缺失类型。Spring Boot 官方自动配置大量使用这种分层结构:先用条件隔离可选依赖,再在满足条件的配置类中声明相关 Bean。

三、@ConditionalOnProperty:配置满足条件才加载

@ConditionalOnProperty 解决的是另一个问题:类存在,但当前用户是否通过配置打开了这个功能?

@Configuration
@ConditionalOnProperty(
        prefix = "template.mq",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = false
)
public class MqConsumerConfiguration {
}

配置文件:

template:
  mq:
    enabled: true

只有配置项满足条件时,这个配置类才会生效。

havingValue 和 matchIfMissing 怎么理解?

havingValue 表示期望值。当写成 true 时,配置值需要匹配字符串 true。不要把它理解成 Java 的 boolean 比较,Spring Boot 条件匹配最终会基于环境中的配置值进行判断。

matchIfMissing 表示配置项不存在时是否匹配:

  • false:没有配置时不匹配,这是默认值
  • true:没有配置时也匹配,相当于默认开启

如果不写 havingValue,条件有一套特殊的默认规则:属性存在且值不是 false 时通常可以匹配。因此,想表达“必须明确写 enabled=true 才开启”,最好显式写 havingValue = "true",并根据需求设置 matchIfMissing。

功能开关的建议

对基础设施功能来说,默认关闭通常更安全,因为使用者必须主动声明依赖和配置。对兼容性很强的基础配置来说,也可以选择默认开启,但必须在文档中写清楚默认行为。

四、两个条件注解为什么经常组合?

一个完整的自动配置通常同时关心两件事:

  1. 依赖是否存在
  2. 使用者是否开启了功能
@Configuration
@ConditionalOnClass(RabbitTemplate.class)
@ConditionalOnProperty(
        prefix = "template.mq",
        name = "enabled",
        havingValue = "true"
)
public class MqAutoConfiguration {
}

多个条件注解放在同一个配置类上时,通常表示这些条件需要同时满足。可以把它理解成:

RabbitMQ 类存在
      AND
template.mq.enabled=true
      ↓
自动配置才继续生效

这就是 Spring Boot 自动配置的核心思想:不是无条件地创建所有 Bean,而是在当前应用环境适合时才提供默认实现。

五、@ConfigurationProperties:把一组配置绑定成对象

为什么不用到处写 @Value?

配置项少的时候,@Value 看起来很方便:

@Value("${template.mq.host}")
private String host;

@Value("${template.mq.port}")
private int port;

但配置一多,就会出现字段分散、前缀重复、类型转换不集中和测试困难等问题。

@ConfigurationProperties 可以把同一个前缀下的配置整体绑定到一个类型:

@ConfigurationProperties(prefix = "template.mq")
public class TemplateMqProperties {

    private boolean enabled = true;
    private String host = "localhost";
    private int port = 5672;
    private String username;
    private String password;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

配置文件:

template:
  mq:
    enabled: true
    host: 192.168.1.100
    port: 5672
    username: admin
    password: secret

它支持哪些能力?

  • 字符串到 int、boolean 等类型的转换
  • Duration、DataSize 等 Spring Boot 类型转换
  • List、Set、Map 等集合
  • 嵌套对象
  • 宽松绑定,例如 virtual-host 可以绑定到 virtualHost
  • 统一的配置元数据和 IDE 提示
  • 在启动阶段集中发现配置类型错误

配置类应该尽量保持什么样子?

配置属性类通常只负责描述配置,不应该在里面创建连接、访问数据库或执行复杂业务。它更像一个类型安全的配置载体:数据从环境进入它,业务 Bean 再读取它。

对于 Spring Boot 3,推荐使用构造器绑定或普通 setter 绑定,并根据是否需要修改来决定字段是否不可变。配置类如果使用构造器绑定,必须确保它通过正确的配置属性注册方式进入容器。

六、@ConfigurationProperties 和 @Value 怎么选?

对比维度@ConfigurationProperties@Value
使用场景一组相关配置单个配置值
类型安全强相对弱,依赖表达式转换
嵌套结构适合不适合
集合配置适合写法复杂
配置元数据支持通常不集中
测试和复用更容易容易分散

选择规则很简单:

  • 只有一个简单值,例如应用名称:可以使用 @Value
  • 一个模块有多个相关配置:优先使用 @ConfigurationProperties
  • 需要 List、Map、Duration 或嵌套对象:优先使用 @ConfigurationProperties
  • 开发公共 Starter:几乎总是应该使用 @ConfigurationProperties

七、@Component:通过扫描注册配置属性 Bean

@ConfigurationProperties 解决绑定问题,但还需要让 TemplateMqProperties 成为 Spring Bean。第一种方式是在类上加 @Component:

@Component
@ConfigurationProperties(prefix = "template.mq")
public class TemplateMqProperties {
    private String host;
    private int port;
}

只要它位于 @ComponentScan 能扫描到的包路径中,Spring 就会发现并注册它。

这种方式适合什么场景?

  • 配置类只在当前业务项目内部使用
  • 项目包结构稳定
  • 不需要把配置属性类单独作为公共 API 发布
  • 使用者明确知道这个类会被组件扫描

它有什么问题?

它依赖组件扫描。如果配置类在扫描范围外,绑定逻辑不会自动生效。并且对于公共 Starter 来说,把 @Component 放在配置属性类上会让组件扫描承担本应由自动配置负责的工作。

八、@EnableConfigurationProperties:显式注册配置属性类

第二种方式是让配置属性类保持普通 Java 类,只标记 @ConfigurationProperties:

@ConfigurationProperties(prefix = "template.mq")
public class TemplateMqProperties {
    private String host;
    private int port;
}

然后在配置类上显式注册:

@Configuration
@EnableConfigurationProperties(TemplateMqProperties.class)
public class MqAutoConfiguration {
}

这里要区分两件事:

  • @ConfigurationProperties:定义绑定前缀和属性结构
  • @EnableConfigurationProperties:启用并注册指定的配置属性类型

它不依赖 @ComponentScan 去发现 TemplateMqProperties,因此非常适合自动配置和 Starter。

它是不是只能用于 Starter?

不是。@EnableConfigurationProperties 也可以用于普通业务项目。只是它在 Starter 中更常见,因为公共配置类不应该依赖使用方的组件扫描路径。

九、@ConfigurationPropertiesScan:批量扫描配置属性类

当项目中有多个配置属性类时,可以使用 @ConfigurationPropertiesScan:

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.properties")
public class Application {
}

它会扫描指定包下标记了 @ConfigurationProperties 的类,并把它们注册为 Bean。

因此,配置属性类注册方式并不只有两种:

注册方式特点适合场景
@Component单个类通过组件扫描注册业务项目内部
@EnableConfigurationProperties显式指定类注册Starter、自动配置、少量明确配置类
@ConfigurationPropertiesScan批量扫描并注册业务项目有多个配置属性类

三者不要同时对同一个配置属性类使用,否则可能产生重复注册或配置意图不清的问题。

十、@ConditionalOnMissingBean:用户自定义优先

自动配置不能只会创建 Bean,还必须给用户留下覆盖默认实现的机会。@ConditionalOnMissingBean 就是为此存在的:

@Bean
@ConditionalOnMissingBean
public MqClient mqClient(TemplateMqProperties properties) {
    return new DefaultMqClient(properties);
}

含义是:如果容器中还没有符合条件的 MqClient,才创建默认实现;如果用户自己提供了 MqClient,自动配置就让位。

这体现了 Spring Boot 的一个重要设计原则:

自动配置提供默认值,但不应该阻止用户覆盖默认值。

如果不加这个条件,Starter 可能无条件注册一个 Bean,导致用户自定义 Bean 时出现冲突,或者默认实现覆盖了用户明确的选择。

十一、把这些注解组合成一个完整自动配置

下面把配置属性、条件判断、显式注册和默认 Bean 组合起来:

@Configuration
@ConditionalOnClass(RabbitTemplate.class)
@EnableConfigurationProperties(TemplateMqProperties.class)
public class MqAutoConfiguration {

    @Bean
    @ConditionalOnProperty(
            prefix = "template.mq",
            name = "enabled",
            havingValue = "true"
    )
    @ConditionalOnMissingBean
    public RabbitTemplate rabbitTemplate(
            TemplateMqProperties properties) {
        return createRabbitTemplate(properties);
    }

    private RabbitTemplate createRabbitTemplate(
            TemplateMqProperties properties) {
        // 根据 properties 创建连接工厂和 RabbitTemplate
        return new RabbitTemplate();
    }
}

这段代码的执行逻辑可以拆成四步:

1. Classpath 中是否存在 RabbitTemplate?
          ↓ 是
2. 注册 TemplateMqProperties,并绑定 template.mq 配置
          ↓
3. template.mq.enabled 是否等于 true?
          ↓ 是
4. 容器中是否已经存在 RabbitTemplate?
          ↓ 不存在
5. 创建默认 RabbitTemplate

如果第 1 步失败,说明依赖不存在;如果第 3 步失败,说明功能没有开启;如果第 4 步发现用户已有 Bean,自动配置就不会重复创建。

十二、自动配置类还需要被 Spring Boot 发现

写完 MqAutoConfiguration 并不代表 Spring Boot 一定会加载它。对于 Spring Boot 3,自动配置通常在以下文件中声明:

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

文件内容可以是一行类名:

com.example.mq.autoconfigure.MqAutoConfiguration

Spring Boot 2 常见的是 spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.mq.autoconfigure.MqAutoConfiguration

因此,一个 Starter 的完整链路是:

AutoConfiguration.imports
        ↓
发现自动配置类
        ↓
条件注解判断是否匹配
        ↓
配置属性类绑定并注册
        ↓
创建用户没有提供的默认 Bean

十三、条件注解的常见组合

除了本文重点介绍的几个注解,自动配置中还经常看到这些条件:

注解作用
@ConditionalOnBean容器中存在指定 Bean 时匹配
@ConditionalOnMissingBean容器中不存在指定 Bean 时匹配
@ConditionalOnResource指定资源存在时匹配
@ConditionalOnWebApplication当前是 Web 应用时匹配
@ConditionalOnNotWebApplication当前不是 Web 应用时匹配
@ConditionalOnExpressionSpEL 表达式满足时匹配
@ConditionalOnJavaJava 版本满足时匹配

这些注解都属于 @ConditionalOnXxx 家族。它们不是在业务代码中替代 if,而是在 Bean 定义阶段决定配置是否参与容器创建。

十四、最容易犯的几个错误

错误一:以为 @ConfigurationProperties 会自动生成 Bean

它只负责描述绑定规则。必须通过 @Component、@EnableConfigurationProperties 或 @ConfigurationPropertiesScan 之一注册。

错误二:把 @EnableConfigurationProperties 理解成“扫描所有配置类”

它通常显式启用指定的配置属性类;需要批量扫描时使用 @ConfigurationPropertiesScan。

错误三:把 @ConditionalOnProperty 当成 boolean 判断

配置环境中的值首先是外部配置值。需要严格开启语义时,明确写 havingValue = "true",并决定缺失时是否匹配。

错误四:自动配置没有 @ConditionalOnMissingBean

没有这个条件,用户可能无法用自己的实现覆盖 Starter 的默认实现。

错误五:配置属性类里直接写业务逻辑

配置类应该是稳定的数据载体。连接创建、重试策略和业务动作应该放到其他 Bean 或服务中。

错误六:只写自动配置类,却忘记注册文件

没有 AutoConfiguration.imports 或 spring.factories,Spring Boot 不知道这个自动配置类的存在。

十五、最终决策树

需要把配置文件绑定成 Java 对象?
    ├── 是 → @ConfigurationProperties
    │       ├── 单个配置类 → @EnableConfigurationProperties
    │       ├── 多个配置类 → @ConfigurationPropertiesScan
    │       └── 业务项目内部且可扫描 → @Component
    └── 否 → 只有一个简单值时考虑 @Value

需要控制 Bean 是否加载?
    ├── 依赖类存在 → @ConditionalOnClass
    ├── 配置开关满足 → @ConditionalOnProperty
    ├── 已有前置 Bean → @ConditionalOnBean
    └── 用户没有自定义 Bean → @ConditionalOnMissingBean

十六、最后怎么记?

Spring Boot 这组注解并不复杂,关键是不要把职责混在一起:

  • @ConfigurationProperties:负责把配置数据绑定到对象
  • @Component:通过组件扫描注册 Bean
  • @EnableConfigurationProperties:显式注册指定的配置属性 Bean
  • @ConfigurationPropertiesScan:批量扫描配置属性 Bean
  • @ConditionalOnClass:依赖存在才加载
  • @ConditionalOnProperty:配置满足才加载
  • @ConditionalOnMissingBean:用户没有提供时才提供默认实现
  • @Value:适合注入单个简单配置值

当你继续阅读 Spring Boot 自动配置源码时,可以始终追问三个问题:

  1. 这个注解是在定义数据、注册对象,还是判断条件?
  2. 它发生在 Bean 创建之前,还是对象已经创建之后?
  3. 它有没有给用户自定义实现留下覆盖空间?

理解这三个问题,Spring Boot 的配置体系就不再是一堆需要死记硬背的注解,而会变成一条清晰的启动链路:发现配置、判断条件、绑定属性、注册 Bean,并在必要时让用户覆盖默认实现。