1. 概述
Spring 表达式语言 (SpEL) 是一种功能强大的表达式语言,支持在运行时查询和操作对象图。它可以与 XML 或基于注释的 Spring 配置一起使用。
该语言有多种可用的运算符:
类型
运营商
算术
+、-、*、/、%、^、div、mod
关系型
<、>、==、!=、<=、>=、lt、gt、eq、ne、le、ge
逻辑性
和、或、非、&&、||、!
有条件的
?:
正则表达式
火柴
2. 运营商
对于这些示例,我们将使用基于注释的配置。有关 XML 配置的更多详细信息可以在本文后面的部分中找到。
SpEL 表达式以 # 符号开头,并用大括号括起来: #{表达式} 。属性可以以类似的方式引用,以 $ 符号开头,并用大括号括起来: ${property.name} 。属性占位符不能包含 SpEL 表达式,但表达式可以包含属性引用:
#{${someProperty} + 2}
在上面的示例中,假设 someProperty 的值为 2,因此结果表达式将为 2 + 2,其计算结果为 4。
2.1.算术运算符
支持所有基本算术运算符。
@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.关系运算符和逻辑运算符
还支持所有基本关系和逻辑运算。
@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 支持所有基本逻辑运算:
@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.条件运算符
条件运算符用于根据某些条件注入不同的值:
@Value("#{2 > 1 ? 'a' : 'b'}") // "a"
private String ternary;
三元运算符用于在表达式内执行紧凑的 if-then-else 条件逻辑。在此示例中,我们尝试检查是否为 真 。
三元运算符的另一个常见用途是检查某个变量是否为 null ,然后返回变量值或默认值:
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;
Elvis 运算符是一种缩短 Groovy 语言中使用的上述情况的三元运算符语法的方法。它也可以在 SpEL 中使用。下面的代码与上面的代码等效:
@Value("#{someBean.someProperty ?: 'default'}") // Will inject provided string if someProperty is null
private String elvis;
2.5.在 SpEL 中使用正则表达式
匹配 运算符可用于检查字符串是否与给定的正则表达式匹配。
@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。访问 列表 和 映射 对象
在 SpEL 的帮助下,我们可以访问上下文中任何 Map 或 List 的内容。我们将创建新的 beanworkersHolder ,它将在 List 和 Map 中存储有关一些工人及其工资的信息:
@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 访问集合的值:
@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. Spring配置中使用
3.1.引用 Bean
在此示例中,我们将了解如何在基于 XML 的配置中使用 SpEL。表达式可用于引用 bean 或 bean 字段/方法。例如,假设我们有以下类:
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
}
现在我们创建一个应用程序上下文,其中使用表达式来注入值:
<bean id="engine" class="com.baeldung.spring.spel.Engine">
<property name="capacity" value="3200"/>
<property name="horsePower" value="250"/>
<property name="numberOfCylinders" value="6"/>
</bean>
<bean id="someCar" class="com.baeldung.spring.spel.Car">
<property name="make" value="Some make"/>
<property name="model" value="Some model"/>
<property name="engine" value="#{engine}"/>
<property name="horsePower" value="#{engine.horsePower}"/>
</bean>
看一下 someCar bean。 someCar 的 engine 和 horsePower 字段使用的表达式分别是对 engine bean 和 horsePower 字段的 bean 引用。
要对基于注释的配置执行相同的操作,请使用 @Value(“#{expression}”) 注释。
3.2.在配置中使用运算符
本文第一部分中的每个运算符都可以在 XML 和基于注释的配置中使用。但是,请记住,在基于 XML 的配置中,我们不能使用尖括号运算符“<”。相反,我们应该使用字母别名,例如 lt (小于)或 le (小于或等于)。对于基于注释的配置,没有这样的限制。
public class SpelOperators {
private boolean equal;
private boolean notEqual;
private boolean greaterThanOrEqual;
private boolean and;
private boolean or;
private String addString;
// Getters and setters
@Override
public String toString() {
// toString which include all fields
}
现在我们将向应用程序上下文添加一个 spelOperators bean:
<bean id="spelOperators" class="com.baeldung.spring.spel.SpelOperators">
<property name="equal" value="#{1 == 1}"/>
<property name="notEqual" value="#{1 lt 1}"/>
<property name="greaterThanOrEqual" value="#{someCar.engine.numberOfCylinders >= 6}"/>
<property name="and" value="#{someCar.horsePower == 250 and someCar.engine.capacity lt 4000}"/>
<property name="or" value="#{someCar.horsePower > 300 or someCar.engine.capacity > 3000}"/>
<property name="addString" value="#{someCar.model + ' manufactured by ' + someCar.make}"/>
</bean>
从上下文中检索该 bean,然后我们可以验证值是否已正确注入:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");
这里我们可以看到 spelOperators bean 的 toString 方法的输出:
[equal=true, notEqual=false, greaterThanOrEqual=true, and=true,
or=true, addString=Some model manufactured by Some make]
4. 以编程方式解析表达式
有时,我们可能想在配置上下文之外解析表达式。幸运的是,使用 SpelExpressionParser 这是可能的。我们可以使用前面示例中看到的所有运算符,但应使用不带大括号和哈希符号的运算符。也就是说,如果我们想在Spring配置中使用带有 + 运算符的表达式,语法为 #{1 + 1}; 当在配置之外使用时,语法很简单 1 + 1 。
在以下示例中,我们将使用上一节中定义的 Car 和 Engine bean。
4.1.使用 表达式解析器
让我们看一个简单的例子:
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();
ExpressionParser 负责解析表达式字符串。在此示例中,SpEL 解析器将简单地将字符串 “Any String” 计算为表达式。不出所料,结果将是 'Any String' 。
与在配置中使用 SpEL 一样,我们可以使用它来调用方法、访问属性或调用构造函数。
Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();
此外,我们可以调用构造函数,而不是直接对文字进行操作:
Expression expression = expressionParser.parseExpression("new String('Any string').length()");
我们还可以以同样的方式访问 String 类的 bytes 属性,从而得到字符串的 byte[] 表示形式:
Expression expression = expressionParser.parseExpression("'Any string'.bytes");
byte[] result = (byte[]) expression.getValue();
我们可以链接方法调用,就像在普通的 Java 代码中一样:
Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
Integer result = (Integer) expression.getValue();
在本例中,结果将为 9,因为我们已将空格替换为空字符串。如果我们不想转换表达式结果,我们可以使用泛型方法 T getValue(Class desiredResultType) ,其中我们可以提供我们想要返回的类的所需类型。请注意,如果返回值无法转换为 desiredResultType, 则会抛出 EvaluationException :
Integer result = expression.getValue(Integer.class);
最常见的用法是提供针对特定对象实例进行计算的表达式字符串:
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 。
但是,如果根对象反复更改,我们可以使用下例所示的机制:
Expression expression = expressionParser.parseExpression("model");
String result = (String) expression.getValue(car);
在这里,我们使用一个参数调用 getValue 方法,该参数表示我们要应用 SpEL 表达式的对象。我们还可以像以前一样使用通用的 getValue 方法:
Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
boolean result = expression.getValue(car, Boolean.class);
4.2.使用 ExpressionParser 设置值
使用通过解析表达式返回的 Expression 对象的 setValue 方法,我们可以设置对象的值。 SpEL 将负责类型转换。默认情况下,SpEL 使用 org.springframework.core.convert.ConversionService 。我们可以在类型之间创建我们自己的自定义转换器。 ConversionService 能够识别泛型,因此它可以与泛型一起使用。让我们看看如何在实践中使用它:
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");
生成的汽车对象将具有从“ Model 3 ”更改为“ Other model ” 的模型 。
4.3.解析器配置
在下面的示例中,我们将使用以下类:
public class CarPark {
private List<Car> cars = new ArrayList<>();
// Getter and setter
}
可以通过使用 SpelParserConfiguration 对象*.* 调用构造函数来配置 ExpressionParser 例如,如果我们尝试将 汽车 对象添加到 CarPark 类的 cars 数组中而不配置解析器,我们将收到如下错误:
EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid
我们可以更改解析器的行为,以允许它在指定索引为 null 时自动创建元素( autoGrowNullReferences, 构造函数的第一个参数),或者自动增长数组或列表以容纳超出其初始大小的元素( autoGrowCollections ,第二个参数)。
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);
生成的 汽车 对象将等于上一个示例中设置为 carPark 对象的 cars 数组的第一个元素的 汽车 对象。
5. 结论
SpEL 是一种功能强大、得到良好支持的表达式语言,可以在 Spring 产品组合中的所有产品中使用。它可用于配置 Spring 应用程序或编写解析器以在任何应用程序中执行更常规的任务。
本文中的代码示例可在链接的 GitHub 存储库中获取。