1. 概述
Spring 表達式語言 (SpEL) 是一種強大的表達式語言,它支持在運行時查詢和操作對象圖。我們可以使用它與基於 XML 或註解的 Spring 配置一起使用。
該語言提供了多種運算符:
| 類型 | 運算符 |
|---|---|
| 算術 | +, -, *, /, %, ^, div, mod |
| 關係 | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
| 邏輯 | and, or, not, &&, ||, ! |
| 條件 | ?: |
| 正則表達式 | matches |
2. 運算符
對於這些示例,我們將使用基於註解的配置。有關 XML 配置的更多詳細信息,請參閲本文後面的部分。
SpEL 表達式以 # 符號開頭,並用花括號括起來:#{expression}。
屬性也可以以類似的方式引用,以 $ 符號開頭,並用花括號括起來:${property.name}。
屬性佔位符不能包含 SpEL 表達式,但表達式可以包含屬性引用:
#{${someProperty} + 2}在上面的示例中,假設 someProperty 的值為 2,則結果表達式將為 2 + 2,這將被評估為 4。
2.1. 算術運算符
SpEL 支持所有基本算術運算符:
@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. 關係和邏輯運算符
SpEL 還支持所有基本關係和邏輯運算:
@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 條件邏輯,這在表達式內部完成。 在這個例子中,我們正在檢查是否存在 true 還是否。
另一個常見的用途是使用三元運算符來檢查某個變量是否為 null,然後返回變量的值或默認值:
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;Elvis 運算符是 Groovy 語言中用於簡化三元運算符語法的便捷方式,常用於“case”語句中。它也同樣在 SpEL 中可用。
這段代碼與上述代碼等效。
@Value("#{someBean.someProperty ?: 'default'}") // Will inject provided string if someProperty is null
private String elvis;2.5. 使用正則表達式在 SpEL 中
我們可以使用matches 運算符來檢查字符串是否與給定的正則表達式匹配。
@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,我們可以訪問上下文中的任何 List 或 Map 對象。
我們將創建一個名為 carPark 的新 Bean,該 Bean 將存儲一些汽車及其駕駛員的信息,存儲在 List 和 Map 中:
@Component("carPark")
public class CarPark {
private List<Car> cars = new ArrayList<>();
private Map<String, Car> carsByDriver = new HashMap<>();
public CarPark() {
Car model1 = new Car();
model1.setMake("Good company");
model1.setModel("Model1");
model1.setYearOfProduction(1998);
Car model2 = new Car();
model2.setMake("Good company");
model2.setModel("Model2");
model2.setYearOfProduction(2005);
cars.add(model1);
cars.add(model2);
carsByDriver.put("Driver1", model1);
carsByDriver.put("Driver2", model2);
}
//Getters and setters
}
現在我們可以使用 SpEL 通過鍵值訪問集合中的值。
@Value("#{carPark.carsByDriver['Driver1']}") // Model1
private Car driver1Car;
@Value("#{carPark.carsByDriver['Driver2']}") // Model2
private Car driver2Car;
@Value("#{carPark.cars[0]}") // Model1
private Car firstCarInPark;
@Value("#{carPark.cars.size()}") // Model2
private Integer numberOfCarsInPark;
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。 engine 和 horsePower 字段是 someCar Bean 中的表達式引用,分別指向 engine Bean 和 horsePower 字段。
要以基於註解的配置方式進行操作,請使用 @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 expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();
ExpressionParser負責解析表達式字符串。 在這個例子中,SpEL解析器會將字符串‘Any String’作為表達式進行評估。 不 Surprisingly,結果將是‘Any String’。
與在配置中使用SpEL類似,我們可以使用它來調用方法、訪問屬性或調用構造函數:
Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();此外,我們還可以通過調用構造函數,而不是直接操作字面量。
Expression expression = expressionParser.parseExpression("new String('Any string').length()");我們還可以訪問 bytes 屬性,類似於 String 類,從而獲得字符串的 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<T> 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);在這種情況下,結果將等於 car 對象字段 model 的值,即 “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");結果的汽車對象將具有模型“Other model”,已從“Model 3“更改。
4.3. 解析器配置
在下面的示例中,我們將使用該類:
public class CarPark {
private List<Car> cars = new ArrayList<>();
// Getter and setter
}可以通過調用構造函數傳入一個 SpelParserConfiguration 對象來配置 ExpressionParser。
例如,如果我們嘗試在不配置解析器的情況下將 car 對象添加到 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);結果的 car 對象將等於從前一個示例中 carPark 對象數組的第一個元素設置的 car 對象。
5. 結論
SpEL 是一種功能強大且得到充分支持的表達式語言,我們可以將其應用於 Spring 產品組合中的所有產品。我們可以使用它來配置 Spring 應用程序,或編寫解析器以執行任何應用程序中的更廣泛任務。