@Value 详解:Spring 属性注入的核心注解

@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 进行自动类型转换,支持大多数常见类型(intlongbooleanListSetMapDateURL 等)。

三、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;
    }
}

五、工作原理

@ValueAutowiredAnnotationBeanPostProcessor 处理。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 中,PropertySourcesPlaceholderConfigurerPropertySourcesPlaceholderConfigurer 自动配置。确保 @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.nameserver.port
  • 一组相关配置用 @ConfigurationProperties,如数据库连接、Redis 配置
  • 需要 SpEL 动态计算时用 @Value

八、总结

@Value 是 Spring 中最基础的配置注入注解,适合注入少数配置项或需要 SpEL 表达式的场景。当配置项较多且结构复杂时,@ConfigurationProperties 是更好的选择。理解 @Value 的解析机制(PropertySourcesPlaceholderConfigurer 处理 ${}StandardBeanExpressionResolver 处理 #{}),有助于排查配置不生效和类型转换错误。

相关推荐
SunnyDays101143 分钟前
如何使用 Python 给 PDF 添加附件:文档附件与附件注释
开发语言·python·pdf
SamChan901 小时前
PDF翻译服务端到端基准测试:4款主流方案的吞吐量、延迟、内存占用对比
服务器·网络·python·ai·pdf·wpf
2401_850481171 小时前
UVA815
java
霸道流氓气质1 小时前
Spring AI 多租户隔离方案
java·人工智能·spring
不会就选b1 小时前
Linux之socket编程(七)----序列化与反序列化(1)
java·linux·服务器
秋名RG1 小时前
Java 工程师学习路线
java·学习方法
Mr.敦的私房菜1 小时前
【SpringEvent】Spring Boot / Spring Framework 事件大全
spring boot·spring
秋名RG1 小时前
Java 基础语法
java·开发语言
java1234_小锋1 小时前
MyBatis-Plus 3.5.15 已全面支持 Spring Boot 4.0 及 Jackson 3.0
java·spring boot·mybatis