Spring 中的 @Value 注解

1、概览

本文将带你了解 Spring 中的 @Value 注解。它用于向 Spring 管理的 Bean 中的字段注入值,可在字段或构造函数/方法上注解。

2、应用设置

创建一个简单的 Spring 应用来介绍该注解的不同用法。

首先,在 Properties 文件中定义想用 @Value 注解注入的值。

Properties 文件如下:

value.from.file=Value got from the file
priority=high
listOfValues=A,B,C

然后,在配置类中定义一个 @PropertySource,指定 Properties 文件的名称以导入该 Properties 文件。

3、示例

通过注解注入 String(这种行为完全是多此一举,这里只是为了演示):

@Value("string value")
private String stringValue;

使用 @PropertySource 注解导入了 Properties 文件后,就可以使用 @Value 注解处理 Properties 文件中的值。

如下,注入 value.from.file 属性值:

@Value("${value.from.file}")
private String valueFromFile;

也可以使用相同的语法从系统属性中设置值。

假设定义了一个名为 systemValue 的系统属性,如下:

@Value("${systemValue}")
private String systemValue;

可以给未定义的属性提供默认值,如下:

@Value("${unknown.param:some default}")
private String someDefault;

如果同一个属性在系统属性和 Properties 文件中都被定义了,那么系统属性将会生效。

假设将一个属性 priority 定义为系统属性,其值为 System property,而在 Properties 文件中定义为其他属性。其最终注入值是 System property

@Value("${priority}")
private String prioritySystemProperty;

有时,我们需要注入一堆值。在 Properties 文件中将它们定义为以逗号分隔的单个属性值,或者定义为系统属性并注入到数组中会比较方便。

Properties 文件的 listOfValues 中定义了以逗号分隔的值,因此如下数组值为 ["A", "B", "C"]

@Value("${listOfValues}")
private String[] valuesArray;

4、使用 SpEL 的高级示例

还可以使用 SpEL 表达式来获取值。

如下,如果有一个名为 priority 的系统属性,那么它的值就会应用到字段中:

@Value("#{systemProperties['priority']}")
private String spelValue;

如果没有定义系统属性,则会分配 null 值。

为了避免这种情况,可以在 SpEL 表达式中提供一个默认值。

如下,如果系统属性未定义,就会把字段值设置为 some default

@Value("#{systemProperties['unknown'] ?: 'some default'}")
private String spelSomeDefault;

此外,还可以使用其他 Bean 的字段值。假设有一个名为 someBean 的 Bean,它的字段 someValue 等于 10

如下,10 将被分配给该字段:

@Value("#{someBean.someValue}")
private Integer someBeanValue;

可以操作属性来获取一个 List。如下,结果是字符串值 ABC 的 List:

@Value("#{'${listOfValues}'.split(',')}")
private List<String> valuesList;

5、Map 注入

可以使用 @Value 注解来注入 Map 属性。

需要在 Properties 文件中以 {key: 'value'} 的形式定义属性:

valuesMap={key1: '1', key2: '2', key3: '3'}

注意,Map 中的 value 必须使用单引号。

现在,可以将 Properties 文件中的值作为 Map 注入:

@Value("#{${valuesMap}}")
private Map<String, Integer> valuesMap;

如果需要获取 Map 中特定 Key 的 Value,只需在表达式中添加 Key 的名称即可:

@Value("#{${valuesMap}.key1}")
private Integer valuesMapKey1;

如果不确定 Map 是否包含某个 Key,应该选择一个更安全的表达式,它不会抛出异常,但会在找不到 Key 时将值设置为 null

@Value("#{${valuesMap}['unknownKey']}")
private Integer unknownMapKey;

还可以为不存在的属性或 Key 设置默认值:

@Value("#{${unknownMap : {key1: '1', key2: '2'}}}")
private Map<String, Integer> unknownMap;

@Value("#{${valuesMap}['unknownKey'] ?: 5}")
private Integer unknownMapKeyWithDefaultValue;

还可以在注入前过滤 Map Entry。

如下,假设只需要获取 value 大于 1 的条目:

@Value("#{${valuesMap}.?[value>'1']}")
private Map<String, Integer> valuesMapFiltered;

还可以使用 @Value 注解注入所有当前系统属性:

@Value("#{systemProperties}")
private Map<String, String> systemPropertiesMap;

6、构造函数注入

使用 @Value 注解时,并不局限于字段注入。还可以将其与构造函数注入一起使用。

如下:

@Component
@PropertySource("classpath:values.properties")
public class PriorityProvider {

    private String priority;

    @Autowired
    public PriorityProvider(@Value("${priority:normal}") String priority) {
        this.priority = priority;
    }

    // Getter 方法
}

在上例中,直接将 priority 注入 PriorityProvider 的构造函数。

注意,还提供了一个默认值,以防找不到该属性。

7、Setter 方法注入

与构造函数注入类似,也可以在 Setter 函数注入中使用 @Value

@Component
@PropertySource("classpath:values.properties")
public class CollectionProvider {

    private List<String> values = new ArrayList<>();

    @Autowired
    public void setValues(@Value("#{'${listOfValues}'.split(',')}") List<String> values) {
        this.values.addAll(values);
    }

    // Getter 方法
}

使用 SpEL 表达式向 setValues 方法注入一个 Value List

8、Record 注入

Java 14 引入了 Record,以方便创建不可变类。Spring 自 6.0.6 版起支持 @Value 用于 Record 注入:

@Component
@PropertySource("classpath:values.properties")
public record PriorityRecord(@Value("${priority:normal}") String priority) {}

如上,直接将值注入 Record 的构造函数。

9、总结

本文介绍了在 Spring 中应用如何使用 @Value 注解把配置文件、系统属性以及及用 SpEL 表达式计算出属性注入到类的构造函数、字段、Setter 方法甚至是 Record 的构造函数中。


Ref:https://www.baeldung.com/spring-value-annotation