1. 概述

本文我们学习 Spring @Value 注解的用法。该注解用于将常量、配置文件属性、以及其他bean的值注入到Spring bean中。

2. 初始化

方便演示之前,我们先准备一份properties文件,里面是@Value要注入的值。配置类上需要使用 @PropertySource 指定我们的文件名。

properties文件内容如下:

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

3. 使用示例

示例一,将字符串(“string value”)直接注入到字段中。这个例子实际没什么用,没人这么干吧 :)。

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

使用 @PropertySource 注解可加载配置文件,然后通过@Value读取配置文件中的值。

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

也可以读取System property。

假设我们定义了一个名为systemValue的系统属性:

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

对于未定义的属性,我们还可以设置默认值。下面如果没找到名为unknown.param的属性,将注入默认值some default

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

如果同一属性既在系统属性中定义又在配置文件中定义,则优先取系统属性的值。

例如,如果我们有一个键名为priority的属性,它在系统属性中值为System property,而在配置文件中设置为其他值,最后读取是System property的值:

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

有时,我们需要注入数组类型的值,可用以逗号进行分隔。

例如,前面的配置文件中listOfValues是以逗号分隔,因此注入的数组值为["A", "B", "C"]

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

4. 高级用法 - SpEL表达式

@Value 注解支持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值,这里获取一个包含字符串值“A”、“B”、“C”的List:

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

5. 使用@ValueMap

我们还可以使用@Value注解注入Map类型属性。

首先,我们需要在属性文件中以{key: 'value'}的形式定义属性:

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

请注意,Map中的值必须用单引号包裹。

现在,我们可以从属性文件中以Map类型注入这个值:

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

如果我们需要从Map中获取特定键的值,只需在表达式中添加键的名字即可:

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

如果我们不确定Map是否包含特定键,应该选择一个不会抛出异常但当键不存在时将值设置为null的安全表达式:

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

我们还可以为可能不存在的属性或键设置默认值:

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

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

在注入前,我们也可以 过滤Map记录

假设我们需要获取值大于1的记录:

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

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

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

6. 在构造函数上使用@Value注解

@Value注解除了用于字段注入。还可以用于构造函数参数上。

让我们实际看看:

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

    private String priority;

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

    // standard getter
}

在上述示例中,我们直接将priority注入到PriorityProvider构造函数中。

请注意,我们还提供了默认值,以防找不到属性。

7. 使用@Value与setter注入

类似构造注入,我们也可以使用@Value与setter注入。

让我们来看一下:

@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);
    }

    // standard getter
}

我们使用SpEL表达式将一个值列表注入到setValues方法中。

8. @Value与Records

Java 14 引入了Records,以方便创建不可变类。自Spring框架6.0.6版本起,它支持@Value对Record的注入:

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

在这里,我们将值直接注入到与Record的构造函数中。

9. 总结

本文详细介绍了如何在配置文件、系统属性和使用SpEL表达式的属性中使用@Value注解的各种方法。

惯例,本文中的示例代码可在GitHub中找到。