乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Boot 概述

    • 章节导读:Spring Boot概述与核心理念
    • Spring Boot是什么
    • Spring Boot与Spring Framework的关系
    • 约定优于配置
  • 第2章 快速入门与第一个应用

    • 章节导读:快速入门与第一个应用
    • SpringApplication
    • 第一个Spring Boot应用
  • 第3章 起步依赖与版本管理

    • 章节导读:起步依赖与版本管理
    • 起步依赖
    • BOM版本管理
  • 第4章 自动配置原理

    • 章节导读:自动配置原理
    • 自动配置原理
    • AutoConfigurationImportSelector
    • 自动配置报告
  • 第5章 核心注解

    • 章节导读:核心注解
    • @SpringBootApplication
    • @EnableAutoConfiguration
    • @ConditionalOnClass
    • @ConditionalOnMissingBean
    • @ConditionalOnBean
    • @ConditionalOnProperty
  • 第6章 外部化配置与属性绑定

    • 章节导读:外部化配置与属性绑定
    • 外部化配置
    • @ConfigurationProperties
    • @Value
    • 配置属性优先级
  • 第7章 Profile 与环境切换

    • 章节导读:Profile与环境切换
    • @Profile
    • 多环境配置文件
  • 第8章 内嵌服务器与部署

    • 章节导读:内嵌服务器与部署
    • 内嵌服务器
    • Fat Jar
  • 第9章 Actuator 与监控

    • 章节导读:Actuator与监控
    • Actuator Health
    • Actuator Info
    • Actuator Metrics
    • 自定义Endpoint
    • 自定义HealthIndicator
  • 第10章 开发工具与最佳实践

    • 章节导读:开发工具与最佳实践
    • Banner自定义
    • 热部署

@ConfigurationProperties 注解

一句话定位:@ConfigurationProperties 是 Spring Boot 的配置属性批量绑定器,把散落在 application.yml 中的扁平化键值对,按前缀自动映射到结构化的 Java POJO 中,让配置代码从 @Value("${xxx}") 的碎片化中解放出来。


定义与作用

@ConfigurationProperties 用于将外部配置属性(properties/yaml 文件、环境变量、命令行参数等)批量绑定到带有该注解的 Java Bean 的属性上。它解决了 @Value 注解在复杂配置场景下的两大痛点:

  1. 碎片化:10 个配置项需要写 10 个 @Value,代码冗长且难以维护
  2. 类型不安全:@Value 注入的是字符串,需手动转换复杂类型(如 List、Map、Duration)

与 @Value 的对比

维度@Value@ConfigurationProperties
绑定方式单个属性,逐个注入批量绑定,整体映射
类型支持基础类型 + SpEL支持复杂类型(List、Map、嵌套对象)
校验支持无可配合 @Validated + JSR-303 校验
松散绑定不支持支持(如 first-name 映射到 firstName)
适用场景简单值注入结构化配置对象

松散绑定(Relaxed Binding) 是 @ConfigurationProperties 的杀手级特性:配置中的 feixiang.student.max-pool-size、feixiang.student.maxPoolSize、FEIXIANG_STUDENT_MAXPOOLSIZE 环境变量,都能正确映射到 Java 对象的 maxPoolSize 属性。


适用位置与常用属性

适用位置

@ConfigurationProperties 可以标注在:

  1. 类级别:标记该类为配置属性载体,通常与 @Component 或 @Configuration 配合使用
  2. @Bean 方法级别:将第三方库的配置对象注册为 Bean
// 方式一:标注在类上(最常用)
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class StudentProperties { ... }

// 方式二:标注在 @Bean 方法上
@Configuration
public class Config {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() { ... }
}

常用属性

属性类型说明示例
prefixString配置属性的前缀,绑定该前缀下的所有子属性prefix = "feixiang.student"
valueStringprefix 的别名,二者等价value = "spring.datasource"
ignoreInvalidFieldsboolean是否忽略类型转换失败的字段默认 false
ignoreUnknownFieldsboolean是否忽略配置中存在的未知字段默认 true

核心原理

配置属性绑定流程

图解释:Spring Boot 启动时,将所有配置源(YAML、环境变量、命令行参数)加载为 PropertySource 并合并到 Environment 中。ConfigurationPropertiesBinder 按 prefix 筛选属性,通过 RelaxedDataBinder 进行松散绑定匹配,再经 ConversionService 类型转换后,反射调用 Setter 方法注入到 POJO 中。

嵌套结构与类型转换

核心组件:

  • ConfigurationPropertiesBinder:负责执行绑定逻辑
  • RelaxedDataBinder:处理松散绑定(驼峰、中划线、下划线、大写环境变量)
  • ConversionService:将字符串配置值转换为目标字段类型

完整示例

场景说明

飞翔科技公司的学生成绩管理系统需要连接多个外部服务:MySQL 数据库、Redis 缓存、阿里云 OSS。架构师白歌要求小崔把所有连接参数统一管理,并且支持不同环境(开发、测试、生产)使用不同的配置值。

小崔决定用 @ConfigurationProperties 创建一个 FeixiangProperties 配置类,将 application.yml 中的 feixiang.student 前缀属性批量绑定进来。

操作前:使用 @Value 的碎片化配置

// 操作前:传统 @Value 方式(痛点明显)
package com.feixiang.student.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class LegacyConfig {
    
    @Value("${feixiang.student.mysql.host}")
    private String mysqlHost;
    
    @Value("${feixiang.student.mysql.port:3306}")
    private int mysqlPort;
    
    @Value("${feixiang.student.mysql.database}")
    private String mysqlDatabase;
    
    @Value("${feixiang.student.redis.host}")
    private String redisHost;
    
    @Value("${feixiang.student.redis.port:6379}")
    private int redisPort;
    
    @Value("${feixiang.student.oss.endpoint}")
    private String ossEndpoint;
    
    @Value("${feixiang.student.oss.bucket}")
    private String ossBucket;
    
    // 需要写 7 个 @Value,且每新增一个配置都要改代码
    // 没有 IDE 自动补全,拼写错误只能在运行时暴露
}

痛点:

  • 7 个配置项需要 7 个 @Value,代码冗长
  • 新增配置必须修改 LegacyConfig 类
  • 不支持嵌套对象,所有属性都是扁平的
  • 不支持 List/Map 等复杂类型

使用该注解的完整代码

小崔改用 @ConfigurationProperties 后,首先定义结构化的 POJO:

package com.feixiang.student.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;

/**
 * 飞翔科技学生系统配置属性
 * 绑定 application.yml 中 feixiang.student 前缀下的所有属性
 */
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class FeixiangProperties {
    
    private Mysql mysql = new Mysql();
    private Redis redis = new Redis();
    private Oss oss = new Oss();
    private List<String> features;  // 支持 List 类型
    private Map<String, String> notifyMap;  // 支持 Map 类型
    
    // Getters and Setters(必须提供,绑定通过反射调用)
    public Mysql getMysql() { return mysql; }
    public void setMysql(Mysql mysql) { this.mysql = mysql; }
    
    public Redis getRedis() { return redis; }
    public void setRedis(Redis redis) { this.redis = redis; }
    
    public Oss getOss() { return oss; }
    public void setOss(Oss oss) { this.oss = oss; }
    
    public List<String> getFeatures() { return features; }
    public void setFeatures(List<String> features) { this.features = features; }
    
    public Map<String, String> getNotifyMap() { return notifyMap; }
    public void setNotifyMap(Map<String, String> notifyMap) { this.notifyMap = notifyMap; }
    
    // 嵌套对象:MySQL 配置
    public static class Mysql {
        private String host;
        private int port = 3306;
        private String database;
        private String username;
        private String password;
        
        // Getters and Setters
        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; }
        public String getDatabase() { return database; }
        public void setDatabase(String database) { this.database = database; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
    }
    
    // 嵌套对象:Redis 配置
    public static class Redis {
        private String host;
        private int port = 6379;
        private int database = 0;
        
        // Getters and Setters
        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; }
        public int getDatabase() { return database; }
        public void setDatabase(int database) { this.database = database; }
    }
    
    // 嵌套对象:OSS 配置
    public static class Oss {
        private String endpoint;
        private String bucket;
        private String accessKey;
        private String secretKey;
        
        // Getters and Setters
        public String getEndpoint() { return endpoint; }
        public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
        public String getBucket() { return bucket; }
        public void setBucket(String bucket) { this.bucket = bucket; }
        public String getAccessKey() { return accessKey; }
        public void setAccessKey(String accessKey) { this.accessKey = accessKey; }
        public String getSecretKey() { return secretKey; }
        public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
    }
}

对应的 application.yml:

# application.yml
feixiang:
  student:
    mysql:
      host: localhost
      port: 3306
      database: student_db
      username: root
      password: feixiang2024
    redis:
      host: localhost
      port: 6379
      database: 1
    oss:
      endpoint: oss-cn-guangzhou.aliyuncs.com
      bucket: feixiang-student-files
      access-key: LTAIxxxxxxxxxxxx
      secret-key: xxxxxxxxxxxxxxxx
    features:
      - cache
      - log
      - monitor
    notify-map:
      sms: "13800138000"
      email: "admin@feixiang.com"
      wechat: "feixiang_alert"

在业务代码中注入使用:

package com.feixiang.student.service;

import com.feixiang.student.config.FeixiangProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SystemConfigService {
    
    private final FeixiangProperties feixiangProperties;
    
    @Autowired
    public SystemConfigService(FeixiangProperties feixiangProperties) {
        this.feixiangProperties = feixiangProperties;
    }
    
    public void printConfig() {
        System.out.println("=== 飞翔科技学生系统配置 ===");
        System.out.println("MySQL: " + feixiangProperties.getMysql().getHost() + ":" 
            + feixiangProperties.getMysql().getPort() + "/" 
            + feixiangProperties.getMysql().getDatabase());
        System.out.println("Redis: " + feixiangProperties.getRedis().getHost() + ":" 
            + feixiangProperties.getRedis().getPort() + "[DB" 
            + feixiangProperties.getRedis().getDatabase() + "]");
        System.out.println("OSS: " + feixiangProperties.getOss().getEndpoint() + "/" 
            + feixiangProperties.getOss().getBucket());
        System.out.println("Features: " + feixiangProperties.getFeatures());
        System.out.println("Notify Map: " + feixiangProperties.getNotifyMap());
    }
}

操作后运行结果及分析

启动应用并调用 printConfig():

=== 飞翔科技学生系统配置 ===
MySQL: localhost:3306/student_db
Redis: localhost:6379[DB1]
OSS: oss-cn-guangzhou.aliyuncs.com/feixiang-student-files
Features: [cache, log, monitor]
Notify Map: {sms=13800138000, email=admin@feixiang.com, wechat=feixiang_alert}

分析:

  1. 批量绑定:application.yml 中 feixiang.student 前缀下的所有属性被一次性绑定到 FeixiangProperties 对象。
  2. 嵌套对象映射:mysql、redis、oss 三个嵌套对象自动实例化并填充属性。
  3. List 类型支持:features 列表被正确解析为 List<String>。
  4. Map 类型支持:notify-map 被正确解析为 Map<String, String>。
  5. 默认值生效:如果 application.yml 中未指定 mysql.port,则使用 POJO 中定义的 private int port = 3306。
  6. 松散绑定:notify-map(YAML)中的中划线写法,自动映射到 Java 的 notifyMap(驼峰)。

易错场景与面试考点

易错场景一:忘记提供 Setter 方法

小崔最初写 FeixiangProperties 时,为了"代码简洁",只写了 Getter,没写 Setter:

// 错误示范:缺少 Setter
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class BadProperties {
    private String host;
    
    public String getHost() { return host; }
    // 没有 setHost(String host)!
}

后果:Spring 的绑定机制通过反射调用 Setter 注入属性值。缺少 Setter 时,字段保持默认值(null / 0 / false),且不报错。小崔在运行时看到 host 为 null,排查了很久才发现是 Setter 缺失。

正确做法:@ConfigurationProperties 绑定依赖 Setter(或构造器绑定,Spring Boot 2.2+ 支持 @ConstructorBinding)。务必为所有需要绑定的字段提供 Setter。

易错场景二:prefix 拼写错误导致全量空值

// 错误示范:prefix 拼写错误
@Component
@ConfigurationProperties(prefix = "feixiang.stuent")  // ← student 拼成了 stuent
public class FeixiangProperties { ... }

后果:application.yml 中的配置全部在 feixiang.student 前缀下,而 POJO 监听的是 feixiang.stuent。由于 ignoreUnknownFields 默认为 true,框架静默忽略所有配置,POJO 所有字段均为默认值。没有报错,但行为完全错误。

正确做法:

  • 仔细核对 prefix 与 application.yml 中的层级完全一致
  • 在 application.yml 中启用 IDE 的 Spring Boot 配置补全(需引入 spring-boot-configuration-processor)
  • 开发阶段可将 ignoreUnknownFields 设为 false,让未知字段报错

面试考点

Q:@ConfigurationProperties 和 @Value 有什么区别?什么时候用哪个?

@Value 适合单个简单属性的注入,支持 SpEL 表达式;@ConfigurationProperties 适合批量、结构化、复杂类型的配置绑定,支持松散绑定和类型安全。当配置项超过 3 个或存在嵌套结构时,优先使用 @ConfigurationProperties。

Q:松散绑定(Relaxed Binding)是什么意思?

Spring Boot 允许配置键与 Java 属性名以多种形式匹配。例如 Java 中的 maxPoolSize 可以匹配配置中的 max-pool-size、max_pool_size、MAX_POOL_SIZE(环境变量)。这使得不同来源的配置(YAML、properties、环境变量)都能无缝映射到同一 POJO。

Q:如何对 @ConfigurationProperties 进行参数校验?

在 POJO 类上标注 @Validated,在字段上使用 JSR-303 注解(如 @NotNull、@Min(1)、@Pattern)。Spring Boot 会在绑定完成后自动执行校验,校验失败抛出 BindException。

Q:@ConfigurationProperties 的绑定是否支持不可变对象?

Spring Boot 2.2+ 支持 @ConstructorBinding,配合 final 字段和全参构造器,可以创建不可变的配置属性对象。此时不再需要 Setter,但构造器参数名必须与配置键匹配。

上一页
外部化配置
下一页
@Value