@Value 详解:Spring 属性注入的核心注解
一、概述
@Value 是 Spring 框架中用于将外部配置注入到 Bean 中的核心注解。它可以从 .properties、.yml 文件、系统属性、环境变量等配置源中读取值,并注入到标注了 @Value 的字段、方法或构造器参数中。
java
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
}
当 Spring 容器初始化 AppConfig Bean 时,appName 字段会被赋值为配置文件中 app.name 键对应的值。
二、基本用法
2.1 注入普通值
java
@Component
public class MyComponent {
// 注入字符串
@Value("Hello World")
private String message;
// 注入数字
@Value("100")
private int number;
// 注入布尔值
@Value("true")
private boolean enabled;
}
硬编码值不常用,@Value 的主要价值在于从配置源读取值。
2.2 从配置文件注入
properties
# application.properties
app.name=myapp
app.timeout=30
app.cache-enabled=true
java
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.timeout}")
private int timeout;
@Value("${app.cache-enabled}")
private boolean cacheEnabled;
}
2.3 提供默认值
如果配置文件中没有该键,应用启动时抛出 IllegalArgumentException。使用默认值可以避免这个问题:
java
@Value("${app.timeout:30}")
private int timeout;
@Value("${app.name:default-app}")
private String appName;
@Value("${app.debug:false}")
private boolean debug;
@Value("${app.servers:server1,server2}")
private List<String> servers;
语法规则:${属性名:默认值}。: 后面就是默认值,格式与属性值的格式一致。
2.4 注入集合
List / 数组
properties
app.servers=server1,server2,server3
java
@Value("${app.servers}")
private List<String> servers;
@Value("${app.servers}")
private String[] serverArray;
Spring 会自动将逗号分隔的字符串拆分成集合或数组。如果配置值包含逗号,需要添加转义或使用其他分隔符。
Set
properties
app.ids=1,2,3,4,5
java
@Value("${app.ids}")
private Set<Integer> ids;
Map
properties
app.features.cache=true
app.features.logging=false
properties
# 或使用紧凑格式
app.features={cache:true, logging:false}
java
@Value("#{${app.features}}")
private Map<String, Boolean> features;
注意 Map 注入需要使用 #{} SpEL 表达式。
2.5 注入系统属性和环境变量
java
@Value("${os.name}")
private String osName;
@Value("${user.home}")
private String userHome;
@Value("${JAVA_HOME}")
private String javaHome; // 环境变量
@Value("${PATH}")
private String path;
2.6 注入对象类型
properties
app.size=1024
app.price=99.99
app.active=true
app.servers=server1,server2
java
@Component
public class DemoComponent {
@Value("${app.size}")
private Long size;
@Value("${app.price}")
private Double price;
@Value("${app.active}")
private Boolean active;
@Value("${app.servers}")
private List<String> servers;
}
Spring 使用 ConversionService 进行自动类型转换,支持大多数常见类型(int、long、boolean、List、Set、Map、Date、URL 等)。
三、SpEL 表达式支持
@Value 支持 Spring Expression Language (SpEL),使用 #{} 语法。
3.1 基础运算
java
@Value("#{10 * 5}")
private int result; // 50
@Value("#{'Hello' + ' ' + 'World'}")
private String message; // Hello World
3.2 调用 Bean 的方法
java
@Component
public class MathUtils {
public int random() {
return new Random().nextInt(100);
}
}
@Component
public class MyComponent {
@Value("#{mathUtils.random()}")
private int randomNumber;
}
3.3 引用其他属性
java
@Value("#{${app.timeout} * 1000}")
private int timeoutInMillis;
@Value("#{${app.name} + ' - ' + ${app.version}}")
private String fullName;
3.4 条件表达式
java
@Value("#{${app.debug:false} ? 'true' : 'false'}")
private String debugText;
@Value("#{${app.timeout:30} > 60 ? 'long' : 'short'}")
private String timeoutType;
3.5 注入静态类
java
@Value("#{T(java.lang.Math).PI}")
private double pi;
@Value("#{T(java.util.UUID).randomUUID().toString()}")
private String uuid;
3.6 集合操作
java
@Value("#{${app.servers:{'default.com'}}[0]}")
private String primaryServer;
@Value("#{${app.ports:{8080,8081,8082}}.size()}")
private int portCount;
3.7 组合注入
properties
app.name=myapp
app.version=1.0
app.timeout=30
java
@Value("App: ${app.name}, Version: ${app.version}, Timeout: ${app.timeout}s")
private String info;
${} 和 #{} 可以嵌套使用:
java
@Value("#{'${app.name}'.toUpperCase()}")
private String upperCaseName;
四、使用位置
4.1 字段注入
最常用的方式,简洁直观。
java
@Component
public class MyComponent {
@Value("${app.name}")
private String appName;
}
4.2 构造器注入
适用于不可变对象,Spring 官方推荐此方式。
java
@Component
public class MyComponent {
private final String appName;
public MyComponent(@Value("${app.name}") String appName) {
this.appName = appName;
}
}
4.3 Setter 方法注入
支持非 final 字段,适合需要重新配置的场景。
java
@Component
public class MyComponent {
private String appName;
@Value("${app.name}")
public void setAppName(String appName) {
this.appName = appName;
}
}
4.4 方法参数注入
java
@Component
public class MyComponent {
private String appName;
@Autowired
public MyComponent(@Value("${app.name}") String appName) {
this.appName = appName;
}
}
五、工作原理
@Value 由 AutowiredAnnotationBeanPostProcessor 处理。Spring 容器在实例化 Bean 并填充属性时,AutowiredAnnotationBeanPostProcessor.postProcessProperties() 会扫描所有带 @Value 注解的字段和方法,解析属性占位符或 SpEL 表达式,将解析后的值通过反射赋值给目标字段。
java
// 简化流程
1. 创建 Bean 实例(通过构造器或工厂)
2. 调用 postProcessProperties()
├── 扫描字段上的 @Value
├── 使用 PropertySourcesPlaceholderConfigurer 解析 ${...}
├── 使用 SpEL 表达式解析 #{...}
├── 通过 ConversionService 进行类型转换
└── 通过反射设置字段值
3. 执行其他后置处理
PropertySourcesPlaceholderConfigurer 是处理 ${} 占位符的 BeanFactoryPostProcessor,在容器刷新时将占位符替换为实际值。StandardBeanExpressionResolver 负责解析 #{} SpEL 表达式。
如果配置文件中缺少 ${} 引用的键且未设置默认值,PropertySourcesPlaceholderConfigurer 会抛出 IllegalArgumentException,导致容器启动失败。
六、常见问题与陷阱
6.1 配置不生效
原因通常是 PropertySourcesPlaceholderConfigurer 未被正确初始化。在 Spring Boot 中,PropertySourcesPlaceholderConfigurer 由 PropertySourcesPlaceholderConfigurer 自动配置。确保 @Value 所在的 Bean 在 Spring 容器中(有 @Component 等注解),且 Spring Boot 启动类有 @SpringBootApplication。
6.2 静态字段无法注入
@Value 不能直接注入静态字段。Spring 容器管理的是实例,静态字段属于类级别。
解决方案:
java
@Component
public class ConfigHolder {
private static String appName;
@Value("${app.name}")
public void setAppName(String name) {
ConfigHolder.appName = name;
}
public static String getAppName() {
return appName;
}
}
6.3 注入 null 值
默认情况下,@Value 会为缺失的键设置 null(如果类型支持),但数字类型会抛出异常。更安全的做法是设置默认值。
java
@Value("${app.timeout:30}")
private int timeout;
6.4 List 注入不支持空格
properties
app.servers=server1, server2, server3
注入后列表包含 "server1"、" server2"、" server3"(带空格)。需要清理空格,或使用 yml 格式。
6.5 Spring Boot 与 Spring 的区别
Spring Boot 对 @Value 的支持更完善,自动配置了 PropertySourcesPlaceholderConfigurer 和多种 PropertySource(命令行参数、环境变量、配置文件等)。在纯 Spring 项目中,需要在 XML 中配置 <context:property-placeholder> 或 @PropertySource。
6.6 Map 注入需要 SpEL
java
@Value("#{${app.features}}")
private Map<String, Boolean> features;
不写 #{} 会导致类型转换错误。
6.7 循环依赖问题
@Value 注入发生在 Bean 实例化之后,不会引起构造器循环依赖。使用构造器注入时,如果两个 Bean 的构造器参数互相引用对方,可能导致循环依赖,与 @Value 本身无关。
七、@Value vs @ConfigurationProperties
| 对比维度 | @Value | @ConfigurationProperties |
|---|---|---|
| 注入方式 | 逐个字段注入 | 批量绑定到对象 |
| 适用场景 | 少量配置项 | 一组相关配置 |
| 类型安全 | 弱(字符串转换) | 强(支持校验注解) |
| SpEL 支持 | ✅ 支持 | ❌ 不支持 |
| 复杂类型 | 需要手动拼接 | 原生支持(List、Map、嵌套对象) |
| 配置文件提示 | 需要 IDE 插件 | ✅ 自动生成元数据 |
| 配置变更刷新 | 需要结合 @RefreshScope |
需要结合 @RefreshScope |
选择建议:
- 个别配置项用
@Value,如app.name、server.port - 一组相关配置用
@ConfigurationProperties,如数据库连接、Redis 配置 - 需要 SpEL 动态计算时用
@Value
八、总结
@Value 是 Spring 中最基础的配置注入注解,适合注入少数配置项或需要 SpEL 表达式的场景。当配置项较多且结构复杂时,@ConfigurationProperties 是更好的选择。理解 @Value 的解析机制(PropertySourcesPlaceholderConfigurer 处理 ${},StandardBeanExpressionResolver 处理 #{}),有助于排查配置不生效和类型转换错误。