10 Spring Expression Language (SpEL)
10.1 簡介
Spring Expression Language是一個強大的表達式語言,支持在運行時查詢和操作對象。這個語言的語法跟Unified EL類似,但是他提供額外的功能,特別是方法調用和基本的字符串模板功能。
雖然Java也有幾種Java表達式語言,像OGNL,MVEL和JBoss EL,而Spring的表達式語言爲SPring提供了一個很好的支持表達式語言,可以用在Spring下的所有產品中。其語言特性是受Spring產品中的項目要求所驅動的,包括基於Spring Tool Suite的eclipse中代碼完成支持的工具要求。也就是說,SpEL是基於不可知論API的技術,在需要的時候允許整合其他的表達式語言實現。
雖然Spring是作爲Spring組合中表達式評估的基礎,但是與Spring並沒有直接的聯繫,所以他是可以低濃度使用的。本章的很多例子都是將SpEL當做一個獨立的表達式語言看待,所以它需要一些引導的基礎類,比如解析器。大多數Spring用戶不需要了解這些基礎設施,主需要評估一下表達式字符串。這個典型用途是整合SpEL到創建基於bean定義的XML或註解中(在 Expression support for defining bean definitions中有體現)。
本章涵蓋表達式語言的特徵,API以及語言語法。在一些地方,Inventor和Inventor的Society類用於表達式評估的目標對象。這些類的聲明將在本章末尾列出。
10.2 功能概述
表達式語言支持以下功能:
- 文字表達
- 布爾和關係運算符
- 正則表達式
- 類表達式
- 訪問屬性,數組,列表,map
- 方法調用
- 關係運算符
- 分配(Assignment)
- 調用構造函數
- Bean引用
- 數組構造
- 內聯列表
- 內聯map
- Ternary operator
- 變量
- 用戶自定義功能
- Collection projection
- Collection selection
- 模板表達式
10.3 Spring的表達式接口評估表達式
本小節介紹SpEL接口和表達式語言的簡單實用,完整的語言引用可以參考”“語言引用(Language Reference)”部分。
下面這些代碼介紹使用SpELl API評估文字表達式”“Hello World”:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = paser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
message變量最後的值爲‘Hello World’。
經常使用的SpEL類和接口位於包org.springframework.expression
中和它的子包spel.support
。
接口ExpressionParser
用於解析表達式字符串的,在這個例子中,表達式字符串是被單括號括起來的文字表達式。而接口Expression則是用來評估之前定義的表達式字符串。當調用parser.parseExpression
和exp.getValue
這兩個方法時,需要分別拋出ParseException和EvaluationException
異常。
SpEL支持很多功能,比如調用方法,訪問屬性和調用構造器。
一個方法調用的例子,我們調用文字字符串的concat
方法:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
message的值是“Hello World!”。
一個調用JavaBean屬性的例子,調用String的屬性Bytes:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
SpEL也支持嵌套屬性,用標準的.符號,像prop1.prop2.prop3和屬性值的設置,也可以訪問公共字段。
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
還可以調用String的構造器:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
注意使用泛型方法public <T> T getValue(Class<T> desiredResultType)
。使用這個方法不需要將表達式的值轉成結果類型。如果值不能轉成類型T
或註冊類型轉換器轉換不成功,會拋出EvaluationException
異常。
SpEL更常用的方法是提供一個針對特定對象實例(根對象)進行評估的表達式字符串。有兩個選項,選擇哪一個取決於。在下面的例子中我們從Inventor類實例中獲取name屬性。
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
例子中的最後一行的name變量的值爲“Nikola Tesla”。StandardEvaluationContext
類就是name
屬性被評估的地方,如果根對象不太可能會改變,在評估文本中只會被設置一次。如果根對象頻繁的更改,就可以調用getValue
,像下面例子一樣:
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = (String) exp.getValue(tesla);
在這個例子中tesla
直接用到getValue
中,表達式評估設備創建和管理一個默認的評估上下文-它不需要提供。
相對來說,創建StandardEvaluationContext的成本略高,因爲在重複利用中建立緩存狀態,能夠更快的執行下後續的表達式評估。基於這個原因,要儘可能的緩存和重利用,而不是爲每一個表達式創建一個新的評估。
在某些情況下可能需要使用配置的評估上下文,但在調用getValue時仍然需要提供一個不同的根對象。getValue在同一個調用中,可以允許兩個特例。在某些情況下,調用的根對象被認爲是覆蓋了評估上下文的任何(可能爲空)特例。
作爲最後一個例子,用上面的例子講一個顯示布爾類型的用法:
Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(context, Boolean.class); // evaluates to true
10.3.1 EvaluationContext接口
EvaluationContext
接口用在評估需要解決屬性,方法,字段的表達式,來執行類型轉換的需求。StandardEvaluationContext
使用反射來操作對象,緩存java.lang.reflect.Method
,java.lang.reflect.Field
和java.lang.reflect.Constructor
實例以提高性能。
StandardEvaluationContext
是根據方法setRootObject()
或把根對象放入構造器中來評估根對象。同樣也可以用方法setVariable()
和registerFunction()
用在表達式中特殊的變量和功能。StandardEvaluationContext
也是可以註冊自定義ConstructorResolver
,MethodResolver
和PropertyAccessor
來擴展SpEL評估表達式的地方。
類型轉換
SpEL默認使用Spring core(org.springframework.core.convert.ConversionService)的轉換服務。這個轉換服務有很多內置的常用的轉換器,但也可以擴展添加類型之間自定義轉換器。此外,它具有泛型感知的強大功能,這意味着,當在表達式中含有泛型時,SpEL將會嘗試轉換以維持任何對象類型正確性。
在實際開發中怎麼用呢?假設使用setValue()被設置一個List屬性,這個屬性實際上是List。SpEL將會在被放置之前把list元素轉成Boolean。一個簡單例子:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
// b will be false
Boolean b = simple.booleanList.get(0);
10.3.2 解析配置
用解析配置對象(org.springframework.expression.spel.SpelParserConfiguration
)可以配置SpEL表達式解析,配置對象控制表達式組件的行爲。例如,如果數組或集合的某個索引元素爲null,配置對象就可以自動創建一個元素。這對使用一組屬性引用的表達式很有用處。當索引數值超過數組或列表本身長度時,會自動的增加他們的長度。
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
當然還可以配置SpEL編譯器的行爲。
10.3.3 SPEL編譯
Spring Framework 4.1包含一個基本的表達式編譯器。表達式通常在評估階段提供很多動態靈活功能,而不是最優的性能。對於偶然的幾次使用表達式這是沒問題的,但是如果是其他組件(比如Spring Integration)使用的話,需要的是性能而不是動態靈活。
而新的SpEL編譯器打算處理這個需求。編譯器將在評估過程中生成一個Java類以體現表達式的行爲,這樣可以獲得更快的表達式評估。由於缺少表達式類,編譯器在執行編譯時會在評估階段就行信息收集。例如,剛開始時,編譯器不知道引用屬性的類型,但是通過第一次的解釋性評估,它就知道類型是什麼。當然,如果不同的表達式元素隨時間而變化的話,編譯器獲得的信息接下來也會有問題發生。所以編譯器適用於類型信息不會發生改變的表達式。
一個基本的表達式像這樣:
someArray[0].someProperty.someOtherProperty < 0.1
表達式設計數組訪問,屬性的引用和數值運算,能很明顯的看到性能的提升。5000此迭代的例子中,之前的評估需要75ms,而使用編譯器評估只需要3ms。
編譯器配置
編譯器不是默認開啓的,有兩種方式開啓。一種是在之前講的解析配置中打開,另一種是通過SpEL嵌入到其他組件的系統屬性打開。本小節會討論這兩種操作。
編譯器運行的模式都在枚舉org.springframework.expression.spel.SpelCompilerMode
中。模式如下:
- OFF - 默認關閉。
- IMMEDIATE - 立即模式,表達式會被儘快執行。這個模式通常是在第一次解釋性評估之後。如果執行失敗,會收到異常報告。
- MIXED - 混合模式中,表達式會在解釋模式和編譯模式之間切換。經過一些解釋運行後會轉成編譯模式;然後如果編譯格式報錯(像之前講的類型變換),就自動轉成解釋模式,然後循環往復。基本上
IMMEDIATE
模式的異常都是內部自己解決了。
IMMEDIATE
模式存在的目的是MIXED模式會導致有副作用的表達式問題。如果一個編譯器表達式在部分成功後爆炸,可能會影響系統的狀態。在這種情況下,調用應該不會重新運行這個模式,因爲一些表達式需要運行兩次。
選擇運行模式之後,用SpelParserConfiguration
配置解析器:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
當編譯器模式確定時,也可以指定類加載器(允許傳null值),編譯器表達式被定義在任何提供的子類加載器中。重要的是確保子類加載器是否確定,它能看到表達式評估過程中的所有類型。如果子類加載器沒有確定的話,會直接使用默認的(通常是表達式評估期間運行的線程的上下文類加載器)。
第二種方法也即是SpEL嵌入到其他組件中時,不太可能通過配置對象進行配置。這種情況下就會使用系統屬性。spring.expression.compiler.mode
屬性可以設置爲SpelCompilerMode
枚舉值(off
, immediate
, 或mixed
)的其中一個。
編譯器限制
雖然已經有了Spring Framework 4.1的基本編譯器框架,但是它並不支持表達式的每個類型。最初的關注點是在關鍵環境中常用的表達式。以下是不支持的表達式:
- 涉及到分配的表達式;
- 依賴轉換器服務的表達式;
- 使用自定義解析器或表達器的表達式;
- 使用selection or projection的表達式。
10.4 Expression support for defining bean definitions
SpEL表達式可以與用於定義BeanDefinitionXML
配置元素的XML或註解兩種方法一起使用。在這兩種情況下,定義表達式的格式爲#{<expression string>}
。
10.4.1 基於XML配置
屬性值或構造器參數可以用表達式表示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
systemProperties
變量被重新定義,所以你可以在自己的表達式中使用。不需要在重新定義的變量前加#
。
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
還可以通過屬性的名稱引用:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
10.4.2 註解配置
@Value註解可以放置在字段,方法或者方法/構造器參數上,表示默認的值。
下面這個例子表示設置一個字段變量的默認值:
public static class FieldValueTestBean
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
以下是展示在setter方法上註解:
public static class PropertyValueTestBean
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
註解用到參數中的例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
10.5 Language Reference
10.5.1 文字表達式
文字表達式支持的類型有字符串,數值(int,real,hex),布爾和null。字符串類型用單引號分割。還要這個但因好的字符型放到雙引號中。
下面例子是文字表達式的簡單使用。通常,實際使用中他不會這麼簡單,而是作爲更復雜的表達式,比如說在正則表達式的一側。
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
10.5.2 Properties, Arrays, Lists, Maps, Indexers
屬性引用是很簡單的:只需要用‘.’表達嵌套的屬性值:
// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
數組和列表的例子:
ExpressionParser parser = new SpelExpressionParser();
// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
teslaContext, String.class);
// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
societyContext, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
societyContext, String.class);
Map的例子:
// Officer's Dictionary
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
10.5.3 Inline lists
Lists可以用{}
表達:
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
{}
本身表達一個空的list。處於對性能考慮,如果列表都是由文字組成,系統會創建一個常量列表而不是每個評估都會建一個新的列表。
10.5.4 Inine Maps
Maps可以直接用{key:value}表示:
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
{:}表示一個空的map。爲性能考慮同上。
10.5.5 Array construction
可以用Java數組構建數組,在構造時就可以 初始化填充這個數組:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
當構造多維數組時目前不充許提供初始化。
10.5.6 Methods
方法包括經典的Java語法。
// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
10.5.7 Operators
Relational operators
關係運算符包括“==”“!=”“<””<=” “>” “>=”.
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
除了支持關係運算,SpEL還支持instanceof
和matches
運算:
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
每個字符操作可以單純的用字母代替。因爲這可以米麪字符在文檔中有特殊含義。以下是等價的符號: lt
(<
), gt
(>
), le
(⇐
), ge
(>=
), eq
(==
), ne
(!=
), div
(/
), mod
(%
), not
(!
)。不區分大小寫。
Logical operators
邏輯運算符有and,or和not。
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Mathematical operators
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
10.5.8 Assignment
Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
10.5.9 Types
T
操作符代表java.lang.Class(the type)的一個實例。
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
10.5.10 Constructors
類名不能與基本類型和String一樣。
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
//create new inventor instance within add method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
10.5.11 Variables
表達式中的變量的表達式語法#variableName
。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context);
System.out.println(tesla.getName()) // "Mike Tesla"
The #this and #root variables
#this
變量指的是當前的評估對象,#object
變量指的是根上下文對象。
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
10.5.12 Functions
可以通過註冊可以在表達式字符串中調用的用戶定義的函數來擴展SpEL。該功能使用該方法使用StandardEvaluationContext
進行註冊。
public void registerFunction(String name, Method m)
Java方法的引用提供方法的實現。例如,以下是反轉字符串的實用方法:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
然後將這個方法註冊到評估上下文中,就可以在表達式中使用它:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
10.5.13 Bean references
如果使用bean解析器配置了評估上下文,則可以通過(@)符號在表達式中查找beans:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);
要訪問工廠bean本身,需要在bean名稱前加(&)符號:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
10.5.14 Ternary Operator (If-Then-Else)
可以使用三元運算符,在表達式中執行if-then-else條件邏輯。一個小例子:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
在這個例子中,會返回‘falseExp’字符串值。一個更實際的例子:
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
10.5.15 Elvis Operator
Elvis操作符縮短了三元操作符,在Groovy語言中使用。在三元操作符中,需要重複一個變量兩次,例如:
String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";
相反,你可以使用Elvis操作符,命名與Elvis相似:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
還有一個更復雜的例子:
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Elvis Presley
10.5.16 Safe Navigation operator
安全導航運算符用於避免拋出NullPointerException
異常,它來自於Groovy語言。通常在引用一個對象時,需要在訪問對象的方法或屬性之前驗證它是否爲空。爲了避免這種情況,安全導航操作會簡單的返回一個null,而不是拋出異常。
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
10.5.17 Collection Selection
選擇是表達式語言中的一個強大的功能,它允許你通過從一些條目裏選擇將一些源集合轉化爲另外一個。
選擇用的語法是.?[selectionExpression]
。它會過濾掉這個集合,然後返回一個包含原始元素子集的新集合。例如,選擇將很快得到Serbian發明者的列表:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
列表和map都可以進行選擇。在前一種情況下,根據單獨列表元素進行評估標準,同時針對map,根據每個map的entry進行評估標準(Java類型Map.Entry
)。Map輸入中有key和value,可以作爲選擇中訪問的屬性。
下面這個表達式將會返回一個由原始元素集合組成的新集合,其中value都小於27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
爲了返回所有選定元素,還可以檢索第一個或最後一個值。匹配第一個元素的語法^[...]
,匹配最後一個元素的語法是$[...]
。
10.5.18 Collection Projection
Projection(翻譯不出來)允許一個集合驅動子表達式的評估,返回一個新的集合,它的語法是![projectionExpression]
。更容易理解的例子,假設有一個inventors的列表,我們想找到他們出生城市的列表。我們將會評估每個inventor輸入‘placeOfBirth.city’。使用projection:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
當然,map也可以使用,同上。
10.5.19 Expression templating
表達式模板允許文本域一個或多個評估塊混合一起。每個評估模塊都可以用自定義的前綴和後綴進行分隔,常用#{}
作爲分隔符。例如:
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
10.6 例子中用到的類
Inventor.java
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality) {
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
PlaceOfBirth.java
package org.spring.samples.spel.inventor;
public class PlaceOfBirth {
private String city;
private String country;
public PlaceOfBirth(String city) {
this.city=city;
}
public PlaceOfBirth(String city, String country) {
this(city);
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String s) {
this.city = s;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}