本文參考自Spring官方文檔 Spring EL。
在Java上有很多表達(dá)式語言,在很多領(lǐng)域有各種各樣的應(yīng)用。我們應(yīng)該很熟悉Java EE的表達(dá)式語言吧,讓我們能在JSP中隨意插入數(shù)據(jù)。Spring也提供了一個(gè)表達(dá)式語言并添加了自己的功能,以便可以方便的和各種Spring框架交互。我們在項(xiàng)目中不需要手動(dòng)管理Spring表達(dá)式的這些接口和實(shí)例,只需要在合適的時(shí)候編寫Spring表達(dá)式,轉(zhuǎn)換器就會(huì)自動(dòng)解析并轉(zhuǎn)換表達(dá)式。
創(chuàng)建和使用解析器
當(dāng)然,為了說明一下Spring表達(dá)式,我們在這里還是手動(dòng)創(chuàng)建一個(gè)解析器來解析表達(dá)式。下面是簡單的單元測試。
public class SpringElTest {
private static ExpressionParser parser = new SpelExpressionParser();
@Test
public void testHelloWorld() {
Expression expression = parser.parseExpression("'你好世界!'");
String result = (String) expression.getValue();
System.out.println(result);
}
}
還可以使用更復(fù)雜的例子。
@Test
public void testStringOperation() {
Expression expression = parser.parseExpression("'你好'.concat('世界!')");
String result = (String) expression.getValue();
System.out.println(result);
expression = parser.parseExpression("'Hello world!'.toUpperCase()");
result = expression.getValue(String.class);
System.out.println(result);
}
Spring文檔解釋了如何創(chuàng)建和使用Spring表達(dá)式的各個(gè)接口、編譯和配置等等。但是一般情況下我們用不到這些功能。這里就只介紹一下Spring El的語法。如果需要詳細(xì)了解這些信息的話還是直接看文檔吧。
語言指南
這部分介紹了Spring EL表達(dá)式的使用。為了省事就直接引用了文檔的代碼了。下面這些代碼沒有說明的話都是Spring文檔的例子。
字面值
表達(dá)式支持各種類型的字面值。字符串字面值需要使用單引號包括,其他類型字面值直接寫就行。
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();
屬性和集合
Spring表達(dá)式支持屬性,只要使用點(diǎn)號引用屬性即可。
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
數(shù)組和列表可以使用方括號語法引用對應(yīng)索引的元素。
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類型也可以使用方括號語法引用鍵對應(yīng)的值。
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");
內(nèi)聯(lián)集合
我們可以直接在表達(dá)式中定義集合,這就是內(nèi)聯(lián)。內(nèi)聯(lián)集合使用花括號語法。
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
內(nèi)聯(lián)Map則要復(fù)雜一點(diǎn),使用類似JSON的語法,鍵和值之間用冒號分隔開。
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);
數(shù)組
數(shù)組使用類似Java的語法,可以給出初始值,多維數(shù)組也受支持。
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);
方法
方法和Java語法一樣。
// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
運(yùn)算符
表達(dá)式中支持各種運(yùn)算符,運(yùn)算規(guī)則和Java規(guī)則類似。唯一需要注意的是空值的處理,假設(shè)有非空值val,那么下面的表達(dá)式恒為真:val > null。這一點(diǎn)需要注意。
// 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);
邏輯運(yùn)算符可以使用and、or和!。
類型
特殊的T運(yùn)算符可以獲取表達(dá)式對象的類型。
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);
構(gòu)造器
在表達(dá)式中,使用new關(guān)鍵字來調(diào)用構(gòu)造器。
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
變量
在表達(dá)式上下文中,我們可以設(shè)置新變量。然后在表達(dá)式中使用#變量名訪問變量。
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"
#this和#root
#this和#root代表了表達(dá)式上下文的對象,#root就是當(dāng)前的表達(dá)式上下文對象,#this則根據(jù)當(dāng)前求值環(huán)境的不同而變化。下面的例子中,#this即每次循環(huán)的值。
// 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);
Bean引用
這是Spring表達(dá)式獨(dú)有的功能,我們可以在表達(dá)式中引用配置文件定義的其他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);
如果需要獲取Bean工廠本身而不是它構(gòu)造的Bean,可以使用&Bean名稱。
Object bean = parser.parseExpression("&foo").getValue(context);
三元運(yùn)算符
和Java的三元運(yùn)算符類似。
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
Elvis運(yùn)算符
在一些編程語言中(比如C#、Kotlin等)提供該功能,語法是?:。意義是當(dāng)某變量不為空的時(shí)候使用該變量,當(dāng)該變量為空的時(shí)候使用指定的默認(rèn)值。
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
安全導(dǎo)航運(yùn)算符
這是來自Groovy的一個(gè)功能,語法是?.,當(dāng)然有些語言也提供了這個(gè)功能。當(dāng)我們對對象的某個(gè)屬性求值時(shí),如果該對象本身為空,就會(huì)拋出空指針異常,如果使用安全導(dǎo)航運(yùn)算符,空對象的屬性就會(huì)簡單的返回空。
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
集合選擇
這有點(diǎn)類似Java 8的Filter流方法,作用是選擇或者說是過濾,語法是集合對象.?[選擇表達(dá)式],Spring會(huì)迭代集合對象的每一個(gè)元素,并使用選擇表達(dá)式判斷該元素是否滿足條件,最后返回由滿足條件的元素組成的新集合。下面的例子就返回了值大于27的新Map。
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
集合投影
這類似Java 8的Map流方法或者SQL語言的選擇語句,作用是將一個(gè)集合中所有元素的某屬性抽取出來,組成一個(gè)新集合。語法是![投影表達(dá)式]。下面的例子選出了由Member的placeOfBirth的city屬性組成的新集合。
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
表達(dá)式模板
表達(dá)式模板使用#{}定義,它允許我們混合多種結(jié)果。下面就是一個(gè)例子,首先Spring會(huì)先對模板中的表達(dá)式求值,在這里是返回一個(gè)隨機(jī)值,然后將結(jié)果和外部的表達(dá)式組合起來。最終的結(jié)果就向下面這樣了。
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// 結(jié)果是 "random number is 0.7038186818312008"
如果表達(dá)式只是一個(gè)簡單的表達(dá)式,就不需要使用模板。只有表達(dá)式有很多表達(dá)式組成時(shí)才需要。