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 的帮助下,我们可以访问上下文中任何 MapList 的内容。我们将创建新的 beanworkersHolder ,它将在 ListMap 中存储有关一些工人及其工资的信息:

@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。 someCarenginehorsePower 字段使用的表达式分别是对 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

在以下示例中,我们将使用上一节中定义的 CarEngine 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 存储库中获取。