Spring 表达式指南

转自:https://www.baeldung.com/spring-expression-language

概述

Spring Expression Language(SpEL)是一种功能强大的表达语言,它支持在运行时查询和操作对象图。 它可以与XML或基于注释的Spring配置一起使用。

有几种可用的语言操作符:

类型 操作符
算数 +, -, * , /, %, ^, div, mod
关系 <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
逻辑 and, or, not, &&, ||, !
条件 ?:
正则 matches

2.操作符

对于这些示例,我们将使用基于注释的配置。 有关XML配置的更多详细信息,可以在本文的后续部分中找到。

SpEL表达式以符号开头,并用大括号括起来:#{expression}。 可以类似的方式引用属性,从$符号开始,并用大括号括起来:$ {[property.name](http://property.name/)}。 属性占位符不能包含SpEL表达式,但表达式可以包含属性引用:

1
#{${someProperty} + 2}

在上面的示例中,假定someProperty的值为2,因此结果表达式将为2 + 2,其计算结果为4。

2.1. 算术运算符

支持所有基本算术运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Value("#{19 + 1}") // 20
private double add;

@Value("#{'String1 ' + 'string2'}") // "String1 string2"
private String addString;

@Value("#{20 - 1}") // 19
private double subtract;

@Value("#{10 * 2}") // 20
private double multiply;

@Value("#{36 / 2}") // 19
private double divide;

@Value("#{36 div 2}") // 18, the same as for / operator
private double divideAlphabetic;

@Value("#{37 % 10}") // 7
private double modulo;

@Value("#{37 mod 10}") // 7, the same as for % operator
private double moduloAlphabetic;

@Value("#{2 ^ 9}") // 512
private double powerOf;

@Value("#{(2 + 2) * 2 + 9}") // 17
private double brackets;

除法和模运算具有字母别名,div代表/mod代表+运算符还可用于连接字符串。

2.2. 关系和逻辑运算符

还支持所有基本的关系和逻辑操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Value("#{1 == 1}") // true
private boolean equal;

@Value("#{1 eq 1}") // true
private boolean equalAlphabetic;

@Value("#{1 != 1}") // false
private boolean notEqual;

@Value("#{1 ne 1}") // false
private boolean notEqualAlphabetic;

@Value("#{1 < 1}") // false
private boolean lessThan;

@Value("#{1 lt 1}") // false
private boolean lessThanAlphabetic;

@Value("#{1 <= 1}") // true
private boolean lessThanOrEqual;

@Value("#{1 le 1}") // true
private boolean lessThanOrEqualAlphabetic;

@Value("#{1 > 1}") // false
private boolean greaterThan;

@Value("#{1 gt 1}") // false
private boolean greaterThanAlphabetic;

@Value("#{1 >= 1}") // true
private boolean greaterThanOrEqual;

@Value("#{1 ge 1}") // true
private boolean greaterThanOrEqualAlphabetic;

所有关系运算符也都具有字母别名。 例如,在基于XML的配置中,我们不能使用包含尖括号(<<=>>=)的运算符。 相反,我们可以使用lt(小于),le(小于或等于),gt(大于)或ge(大于或等于)。

2.3. 逻辑运算符

SpEL支持所有基本逻辑运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Value("#{250 > 200 && 200 < 4000}") // true
private boolean and;

@Value("#{250 > 200 and 200 < 4000}") // true
private boolean andAlphabetic;

@Value("#{400 > 300 || 150 < 100}") // true
private boolean or;

@Value("#{400 > 300 or 150 < 100}") // true
private boolean orAlphabetic;

@Value("#{!true}") // false
private boolean not;

@Value("#{not true}") // false
private boolean notAlphabetic;

与算术和关系运算符一样,所有逻辑运算符也具有字母别名。

2.4. 条件运算符

条件运算符用于根据某些条件注入不同的值:

1
2
@Value("#{2 > 1 ? 'a' : 'b'}") // "a"
private String ternary;

三元运算符用于在表达式内部执行紧凑的if-then-else条件逻辑。 在此示例中,我们尝试检查是否为真。

三元运算符的另一个常见用法是检查某个变量是否为null,然后返回变量值或默认值:

1
2
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;

Elvis运算符是一种缩短Groovy语言中使用的上述情况的三元运算符语法的方法。 它也可以在SpEL中使用。 下面的代码与上面的代码等效:

1
2
@Value("#{someBean.someProperty ?: 'default'}") // 如果someProperty为空的话,注入提供的字符串
private String elvis;

2.5. 在SpEL中使用正则表达式

matchs运算符可用于检查字符串是否与给定的正则表达式匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Value("#{'100' matches '\\d+' }") // true
private boolean validNumericStringResult;

@Value("#{'100fghdjf' matches '\\d+' }") // false
private boolean invalidNumericStringResult;

@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") // true
private boolean validAlphabeticStringResult;

@Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") // false
private boolean invalidAlphabeticStringResult;

@Value("#{someBean.someValue matches '\d+'}") // true if someValue contains only digits
private boolean validNumericValue;

2.6 访问List和Map对象

借助SpEL,我们可以在上下文中访问任何Map或List的内容。 我们将创建新的bean workerHolder,它将在List和Map中存储有关一些工人及其工资的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component("workersHolder")
public class WorkersHolder {
private List<String> workers = new LinkedList<>();
private Map<String, Integer> salaryByWorkers = new HashMap<>();

public WorkersHolder() {
workers.add("John");
workers.add("Susie");
workers.add("Alex");
workers.add("George");

salaryByWorkers.put("John", 35000);
salaryByWorkers.put("Susie", 47000);
salaryByWorkers.put("Alex", 12000);
salaryByWorkers.put("George", 14000);
}

//Getters and setters
}

现在我们可以使用SpEL访问集合的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Value("#{workersHolder.salaryByWorkers['John']}") // 35000
private Integer johnSalary;

@Value("#{workersHolder.salaryByWorkers['George']}") // 14000
private Integer georgeSalary;

@Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000
private Integer susieSalary;

@Value("#{workersHolder.workers[0]}") // John
private String firstWorker;

@Value("#{workersHolder.workers[3]}") // George
private String lastWorker;

@Value("#{workersHolder.workers.size()}") // 4
private Integer numberOfWorkers;

3. 以编程方式解析表达式

有时,我们可能想在配置上下文之外解析表达式。 幸运的是,使用SpelExpressionParser可以做到这一点。 我们可以使用前面示例中看到的所有运算符,但应使用不带花括号和哈希符号的运算符。 也就是说,如果要在Spring配置中使用带+运算符的表达式,则语法为#{1 + 1}; 在配置之外使用时,语法仅为1 +1

在以下示例中,我们将使用以下类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Engine {
private int capacity;
private int horsePower;
private int numberOfCylinders;

// Getters and setters
}

public class Car {
private String make;
private int model;
private Engine engine;
private int horsePower;

// Getters and setters
}

3.1. 使用ExpressionParser

让我们看一个简单的例子:

1
2
3
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();

ExpressionParser负责解析表达式字符串。 在此示例中,SpEL解析器将简单地将字符串“ Any String”作为表达式进行求值。 毫不奇怪,结果将是“Any String”。

与在配置中使用SpEL一样,我们可以使用它来调用方法,访问属性或调用构造函数。

1
2
Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();

另外,我们可以直接调用构造函数,而不是直接对文字进行运算:

1
Expression expression = expressionParser.parseExpression("new String('Any string').length()")

我们还可以通过相同的方式访问String类的bytes属性,从而得到字符串的byte []表示形式:

1
2
Expression expression = expressionParser.parseExpression("'Any string'.bytes");
byte[] result = (byte[]) expression.getValue();

我们可以链接方法调用,就像普通的Java代码一样:

1
2
Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
Integer result = (Integer) expression.getValue();

在这种情况下,结果将为9,因为我们已用空字符串替换了空格。 如果我们不希望转换表达式结果,则可以使用通用方法T getValue(Class <T> requiredResultType),在该方法中,我们可以提供想要返回的类的所需类型。 请注意,如果不能将返回值强制转换为期望的结果类型,则将引发EvaluationException:

1
Integer result = expression.getValue(Integer.class);

最常见的用法是提供一个针对特定对象实例评估的表达式字符串:

1
2
3
4
5
6
7
8
9
10
Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("model");

EvaluationContext context = new StandardEvaluationContext(car);
String result = (String) expression.getValue(context);

在这种情况下,结果将等于汽车对象“Model 3”的模型字段的值。 StandardEvaluationContext类指定将针对其评估表达式的对象。

创建上下文对象后,无法更改它。 StandardEvaluationContext的构造成本很高,并且在重复使用期间,它会建立缓存状态,从而使后续的表达式评估能够更快地执行。 由于缓存,在根对象不更改的情况下尽可能重用StandardEvaluationContext是一个好习惯。

但是,如果根对象反复更改,则可以使用以下示例中所示的机制:

1
2
Expression expression = expressionParser.parseExpression("model");
String result = (String) expression.getValue(car);

在这里,我们调用带有参数的getValue方法,该参数表示要向其应用SpEL表达式的对象。 我们也可以像以前一样使用通用的getValue方法:

1
2
Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
boolean result = expression.getValue(car, Boolean.class);

3.2. 使用ExpressionParser设置值

在通过解析表达式返回的Expression对象上使用setValue方法,可以在对象上设置值。 SpEL将负责类型转换。 默认情况下,SpEL使用org.springframework.core.convert.ConversionService。 我们可以在类型之间创建自己的自定义转换器。 ConversionService具有泛型意识,因此可以与泛型一起使用。 让我们看一下如何在实践中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

CarPark carPark = new CarPark();
carPark.getCars().add(car);

StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser();
expressionParser.parseExpression("cars[0].model").setValue(context, "Other model");

生成的汽车对象将具有“Other model”模型,该模型已从“Model 3”更改为。

3.3. 解析器配置

在下面的示例中,我们将使用以下类:

1
2
3
4
5
public class CarPark {
private List<Car> cars = new ArrayList<>();

// Getter and setter
}

可以通过使用SpelParserConfiguration对象调用构造函数来配置ExpressionParser。 例如,如果我们尝试在不配置解析器的情况下将car对象添加到CarPark类的cars数组中,则会出现如下错误:

1
EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid

我们可以更改解析器的行为,以允许解析器在指定索引为null时自动创建元素(autoGrowNullReferences,构造函数的第一个参数),或自动增长数组或列表以容纳超出其初始大小的元素(autoGrowCollections ,第二个参数)。

1
2
3
4
5
6
7
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser(config);
expressionParser.parseExpression("cars[0]").setValue(context, car);

Car result = carPark.getCars().get(0);

生成的car对象将等于在上一个示例中被设置为carPark对象的cars数组的第一个元素的car对象。

4.结论

SpEL是一种功能强大且得到良好支持的表达语言,可以在Spring产品组合中的所有产品中使用。 它可用于配置Spring应用程序或编写解析器以在任何应用程序中执行更多常规任务。

本文中的代码示例 [GITHUB]。