如何在 Spring Boot 中注入配置属性?(@Value vs @ConfigurationProperties)
在 Spring Boot 中,注入 application.properties 或 application.yml 中的配置属性主要有两种方式:@Value 和 @ConfigurationProperties。
以下是这两种方式的详细用法、对比以及最佳实践建议。
1. 使用 @Value 注解
这是 Spring 框架原生提供的方式,适合注入简单、独立的配置项。
示例代码
假设 application.yml 如下:
yaml
app:
name: MySpringApp
timeout: 5000
在 Java 代码中注入:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
// 1. 基本注入
@Value("${app.name}")
private String appName;
// 2. 注入并提供默认值(防止配置不存在时报错)
@Value("${app.timeout:3000}")
private int timeout;
// 3. 注入系统属性或 SpEL 表达式
@Value("#{systemProperties['os.name']}")
private String osName;
public void printInfo() {
System.out.println("App: " + appName + ", Timeout: " + timeout);
}
}
特点
- 简单直接:不需要创建额外的类。
- 支持 SpEL:可以使用 Spring 表达式语言(如
#{...})进行复杂计算。 - 弱类型:通常用于 String、int、boolean 等简单类型,处理 List/Map 比较麻烦。
2. 使用 @ConfigurationProperties 注解
这是 Spring Boot 特有的方式,它将配置文件中的一组属性绑定到一个 Java Bean(POJO) 上。这是类型安全的,也是 Spring Boot 推荐的方式。
示例代码
假设 application.yml 如下(包含层级和列表):
yaml
myapp:
database:
url: jdbc:mysql://localhost:3306/db
username: root
servers:
- 192.168.1.1
- 192.168.1.2
步骤 1:创建配置类
java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
// prefix 指定配置的前缀
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
// 必须有 getter/setter (或者使用 Lombok @Data)
private Database database;
private List<String> servers;
// 嵌套类
public static class Database {
private String url;
private String username;
// getter & setter ...
}
// getter & setter for servers and database ...
}
步骤 2:在业务中使用
java
@Service
public class MyService {
private final MyAppProperties properties;
// 构造器注入
public MyService(MyAppProperties properties) {
this.properties = properties;
}
public void doWork() {
System.out.println("DB Url: " + properties.getDatabase().getUrl());
System.out.println("Server 1: " + properties.getServers().get(0));
}
}
特点
- 结构化:完美支持层级结构、List 和 Map。
- 类型安全:自动进行类型转换。
- 松散绑定 (Relaxed Binding):
first-name(kebab-case) 可以自动映射到firstName(camelCase)。 - 支持 JSR-303 校验:可以在字段上加
@NotNull、@Min等注解进行校验。 - IDE 提示:配合
spring-boot-configuration-processor依赖,在写 yml 文件时会有自动补全提示。
3. 核心对比:@Value vs @ConfigurationProperties
| 特性 | @Value | @ConfigurationProperties |
|---|---|---|
| 核心功能 | 注入单个属性值 | 批量绑定一组属性到对象 |
| 松散绑定 | 不支持 (必须精准匹配 key) | 支持 (kebab-case, camelCase 等互通) |
| 复杂类型 (List/Map) | 支持较差 (需用 SpEL 解析) | 支持极好 (自动映射) |
| JSR-303 数据校验 | 不支持 | 支持 (如 @Validated) |
| SpEL 表达式 | 支持 (#{...}) |
不支持 |
| 元数据支持 (IDE提示) | 无 | 有 (写配置时有自动补全) |
| 适用场景 | 简单的、零散的配置 | 模块化的、复杂的、多层级的配置 |
4. 进阶:不可变配置 (Constructor Binding)
从 Spring Boot 2.2 开始(并在 3.0 中成为主流),你可以使用构造器绑定来创建不可变的配置类(即字段为 final),这更加安全。
java
import org.springframework.boot.context.properties.ConfigurationProperties;
// 注意:这里不需要 @Component,通常在主类或配置类上用 @EnableConfigurationProperties(AppConfig.class) 激活
// 或者在 Spring Boot 2.6+ 可以直接加 @ConfigurationPropertiesScan
@ConfigurationProperties(prefix = "app")
public record AppConfig(String name, int timeout) {
// 使用 Java 14+ record 或普通的 class + final 字段 + @ConstructorBinding
}
(注:Spring Boot 3.0+ 默认支持构造器绑定,如果是旧版本可能需要 @ConstructorBinding 注解)
5. 总结与建议
首选
@ConfigurationProperties:- 如果你在定义一组相关的配置(例如数据库连接池配置、自定义模块的开关配置)。
- 如果你需要 IDE 的自动补全提示。
- 如果你需要配置校验。
使用
@Value:- 当配置项非常少(只有 1-2 个),且不属于任何逻辑分组时。
- 当你需要使用 SpEL 表达式进行动态计算时。
- 在单元测试中临时注入某个值时。
最佳实践: 尽量避免在业务代码中散落大量的 @Value,这会导致代码难以维护。将配置封装到 @ConfigurationProperties 类中,可以让业务逻辑与配置读取解耦。