🎯 一、为什么要用 @ConfigurationProperties
?(背景)
❌ 传统方式的问题:@Value("${xxx}")
java
@Value("${acme.remote-address}")
private String remoteAddress;
@Value("${acme.security.username}")
private String username;
这种方式的问题:
- 写法繁琐,每个属性都要写一个
@Value
- 没有结构化,配置分散
- 不支持嵌套对象(如
security.username
) - 不支持类型转换(比如自动转
InetAddress
、Duration
) - 不支持校验(比如不能为空)
- IDE 无法提示你有哪些配置项可用
✅ 解决方案:@ConfigurationProperties
------ 类型安全的配置类
它允许你定义一个 POJO 类 ,把所有相关的配置集中在一起,并通过 属性绑定机制 自动从 application.yml
、环境变量等来源填充这些值。
🧱 二、基本用法:JavaBean 风格(可变类)
java
@ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled = false;
private InetAddress remoteAddress;
private final Security security = new Security();
// getter 和 setter 必须有(除了某些特殊情况)
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public InetAddress getRemoteAddress() { return remoteAddress; }
public void setRemoteAddress(InetAddress remoteAddress) { this.remoteAddress = remoteAddress; }
public Security getSecurity() { return security; }
public static class Security {
private String username;
private String password;
private List<String> roles = Arrays.asList("USER"); // 默认值
// getter/setter...
}
}
对应的 application.yml
:
yaml
acme:
enabled: true
remote-address: 192.168.1.1
security:
username: admin
password: 123456
roles:
- USER
- ADMIN
✅ 特点:
- 所有以
acme.
开头的配置都会绑定到这个类上。 - 支持嵌套对象(
security
是内部类)。 - 支持默认值(
roles
初始化为["USER"]
)。 - 支持类型转换(
remote-address: 192.168.1.1
→InetAddress
)。
🔒 三、进阶用法:构造器绑定(Constructor Binding)------ 推荐用于不可变对象
如果你想让配置类是 不可变的(immutable) ,可以使用 @ConstructorBinding
。
java
@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
// 只有 getter,没有 setter
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
}
}
✅ 优点:
- 线程安全,不可变。
- 更符合函数式编程思想。
- 使用
@DefaultValue("USER")
设置默认值。
⚠️ 注意:使用构造器绑定时,必须启用配置扫描或
@EnableConfigurationProperties
。
🔍 四、如何启用 @ConfigurationProperties
?
Spring Boot 不会自动扫描所有 @ConfigurationProperties
类,你需要显式启用。
方法 1:使用 @EnableConfigurationProperties
(适合少量类)
java
@Configuration
@EnableConfigurationProperties(AcmeProperties.class)
public class MyConfig {
}
方法 2:使用 @ConfigurationPropertiesScan
(推荐,类似组件扫描)
java
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.acme") // 扫描该包下的所有 @ConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
🔄 五、松散绑定(Relaxed Binding)------ 不区分命名风格
Spring Boot 支持多种命名方式自动映射到 Java 字段。
配置项写法(YAML/环境变量) | → 映射到 Java 字段 |
---|---|
acme.remote-address |
→ remoteAddress |
acme.remote_address |
→ remoteAddress |
acme.remoteAddress |
→ remoteAddress |
ACME_REMOTE_ADDRESS |
→ remoteAddress |
✅ 建议:YAML 文件用 kebab-case (短横线),环境变量用 大写下划线。
📦 六、复杂类型的自动转换(Spring Boot 特色功能)
Spring Boot 能自动将字符串转换为常见类型:
1. 时间 Duration(持续时间)
java
@ConfigurationProperties("app")
public class AppProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30); // 默认30秒
}
支持写法:
30s
→ 30 秒PT30S
→ ISO 格式30
→ 毫秒(除非指定了单位)
单位:ns
, us
, ms
, s
, m
, h
, d
2. 数据大小 DataSize(如内存、文件大小)
java
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
支持写法:
10MB
→ 10 兆字节512B
→ 512 字节2
→ 字节(默认)
单位:B
, KB
, MB
, GB
, TB
3. 日期 Period(时间段)
java
private Period maintenanceWindow = Period.ofDays(3);
支持写法:
3d
→ 3 天P3D
→ ISO 格式1y2m
→ 1年2月
✅ 七、配置校验(Validation)
你可以像校验普通 Bean 一样校验配置类。
java
@ConfigurationProperties("acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
// ...
public static class Security {
@NotEmpty
private String username;
}
}
@Validated
:开启校验@Valid
:确保嵌套对象也被校验- 使用 JSR-303 注解(如
@NotNull
,@NotEmpty
)
校验会在应用启动时自动执行,如果配置不合法,直接报错,防止运行时问题。
🧩 八、绑定 Map 和 List 的注意事项
Map 的 key 包含特殊字符?
yaml
acme:
map:
"[/path1]": "value1"
"[key.with.dot]": "value2"
⚠️ 如果不加 []
,.
和 /
会被当作层级分隔符,导致错误绑定。
List 合并规则(重要!)
yaml
acme:
list:
- name: item1
---
spring:
profiles: dev
acme:
list:
- name: item2
👉 结果不是合并,而是覆盖!
- 非 dev 环境:
[item1]
- dev 环境:
[item2]
(item1 被完全替换)
❗ List 不支持"合并",只支持"覆盖"。如果要合并,需手动处理。
🔗 九、注入第三方组件的配置
你也可以给第三方库的 Bean 绑定配置:
java
@Bean
@ConfigurationProperties("my.datasource")
public HikariDataSource dataSource() {
return new HikariDataSource();
}
这样 my.datasource.*
的配置会自动绑定到这个数据源上。
💡 十、@ConfigurationProperties
vs @Value
对比
功能 | @ConfigurationProperties |
@Value |
---|---|---|
松散绑定(relaxed binding) | ✅ 支持 | ❌ 有限支持 |
元数据支持(IDE 提示) | ✅ 自动生成 | ❌ 不支持 |
SpEL 表达式 | ❌ 不支持 | ✅ 支持 |
类型转换(Duration, DataSize) | ✅ 自动支持 | ❌ 需手动 |
结构化配置(嵌套对象) | ✅ 支持 | ❌ 不支持 |
配置校验 | ✅ 支持 | ❌ 不支持 |
✅ 结论:优先使用
@ConfigurationProperties
做配置类,@Value
仅用于简单场景或 SpEL 表达式。
🛠 十一、最佳实践总结
- 每个模块定义一个
@ConfigurationProperties
类 ,如DatabaseProperties
,MailProperties
。 - 使用
@ConstructorBinding
+@DefaultValue
创建不可变配置。 - 使用
@ConfigurationPropertiesScan
自动扫描。 - 使用
@DurationUnit
,@DataSizeUnit
等注解明确单位。 - 使用
@Validated
+@Valid
进行配置校验。 - YAML 配置使用 kebab-case (如
server.port
)。 - 环境变量使用 大写下划线 (如
SERVER_PORT=8080
)。 - 避免在
@ConfigurationProperties
中注入其他 Bean(它应该只负责"读配置")。
🧪 示例:完整配置类
java
@ConstructorBinding
@ConfigurationProperties("app.database")
@Validated
public class DatabaseProperties {
@DurationUnit(ChronoUnit.SECONDS)
private final Duration connectionTimeout;
@DataSizeUnit(DataUnit.MEGABYTES)
private final DataSize maxPoolSize;
@Valid
private final Pool pool;
public DatabaseProperties(Duration connectionTimeout, DataSize maxPoolSize, Pool pool) {
this.connectionTimeout = connectionTimeout;
this.maxPoolSize = maxPoolSize;
this.pool = pool;
}
public static class Pool {
@Min(1)
private final int size;
public Pool(int size) {
this.size = size;
}
// getter...
}
}
yaml
app:
database:
connection-timeout: 30s
max-pool-size: 100MB
pool:
size: 10
✅ 总结一句话:
@ConfigurationProperties
是 Spring Boot 提供的 类型安全、结构化、可校验、支持自动转换 的配置管理方式,应优先于@Value
使用,尤其适合管理复杂、嵌套、有默认值或单位的配置。
如果你正在开发一个 Spring Boot 项目,建议:
- 把所有配置集中到
@ConfigurationProperties
类中。 - 用
@ConstructorBinding
构建不可变对象。 - 加上校验和单位注解。
- 启用
@ConfigurationPropertiesScan
。
这样你的配置会更清晰、更安全、更容易维护。
需要我根据你的实际项目结构写一个示例吗?