@ConfigurationProperties 注解
一句话定位:
@ConfigurationProperties是 Spring Boot 的配置属性批量绑定器。它把散落在application.yml或application.properties中的扁平化键值对,自动映射到结构化的 Java POJO 中,让配置代码从@Value("${xxx}")的碎片化中解放出来。
定义与作用
@ConfigurationProperties 用于将外部配置属性(properties/yaml 文件、环境变量、命令行参数等)批量绑定到带有该注解的 Java Bean 的属性上。它解决了 @Value 注解在复杂配置场景下的两大痛点:
- 碎片化:10 个配置项需要写 10 个
@Value,代码冗长且难以维护 - 类型不安全:
@Value注入的是字符串,需手动转换复杂类型(如 List、Map、Duration)
与 @Value 的对比
| 维度 | @Value | @ConfigurationProperties |
|---|---|---|
| 绑定方式 | 单个属性,逐个注入 | 批量绑定,整体映射 |
| 类型支持 | 基础类型 + SpEL | 支持复杂类型(List、Map、嵌套对象) |
| 校验支持 | 无 | 可配合 @Validated + JSR-303 校验 |
| 松散绑定 | 不支持 | 支持(如 first-name 映射到 firstName) |
| 适用场景 | 简单值注入 | 结构化配置对象 |
松散绑定(Relaxed Binding) 是 @ConfigurationProperties 的杀手级特性:配置中的 spring.datasource.max-pool-size、spring.datasource.maxPoolSize、SPRING_DATASOURCE_MAXPOOLSIZE 环境变量,都能正确映射到 Java 对象的 maxPoolSize 属性。
适用位置与常用属性
适用位置
@ConfigurationProperties 可以标注在:
- 类级别:标记该类为配置属性载体,通常与
@Component或@Configuration配合使用 - @Bean 方法级别:将第三方库的配置对象注册为 Bean
// 方式一:标注在类上(最常用)
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class StudentProperties { ... }
// 方式二:标注在 @Bean 方法上
@Configuration
public class Config {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() { ... }
}
常用属性
| 属性 | 类型 | 说明 | 示例 |
|---|---|---|---|
prefix | String | 配置属性的前缀,绑定该前缀下的所有子属性 | prefix = "feixiang.student" |
value | String | prefix 的别名,二者等价 | value = "spring.datasource" |
ignoreInvalidFields | boolean | 是否忽略类型转换失败的字段 | 默认 false |
ignoreUnknownFields | boolean | 是否忽略配置中存在的未知字段 | 默认 true |
核心原理
配置属性绑定流程
嵌套结构与类型转换
核心组件:
ConfigurationPropertiesBinder:负责执行绑定逻辑RelaxedDataBinder:处理松散绑定(驼峰、中划线、下划线、大写环境变量)ConversionService:将字符串配置值转换为目标字段类型
完整示例
场景简述
飞翔科技公司的学生成绩管理系统需要连接多个外部服务:MySQL 数据库、Redis 缓存、阿里云 OSS。架构师白歌要求小崔把所有连接参数统一管理,并且支持不同环境(开发、测试、生产)使用不同的配置值。
小崔决定用 @ConfigurationProperties 创建一个 FeixiangProperties 配置类,将 application.yml 中的 feixiang.* 前缀属性批量绑定进来。
操作前:使用 @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;
/**
* 飞翔科技学生系统配置属性
* 绑定 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 类型
// 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; }
// 嵌套对象: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
在业务代码中注入使用:
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());
}
}
操作后运行结果及分析
启动应用并调用 printConfig():
=== 飞翔科技学生系统配置 ===
MySQL: localhost:3306/student_db
Redis: localhost:6379[DB1]
OSS: oss-cn-guangzhou.aliyuncs.com/feixiang-student-files
Features: [cache, log, monitor]
分析:
- 批量绑定:
application.yml中feixiang.student前缀下的所有属性被一次性绑定到FeixiangProperties对象。 - 嵌套对象映射:
mysql、redis、oss三个嵌套对象自动实例化并填充属性。 - List 类型支持:
features列表被正确解析为List<String>。 - 默认值生效:如果
application.yml中未指定mysql.port,则使用 POJO 中定义的private int port = 3306。
易错场景与面试考点
易错场景一:忘记提供 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,但构造器参数名必须与配置键匹配。