SpEL的全稱叫做Spring Expression Language。通常是為了在XML或者注解里面方便求值用的,通過編寫#{ }這樣的格式,即可使用。
Bean定義中的使用
XML配置
可以用SpEL設置屬性或構造函數參數值,如下示例所示:
<bean id="numberGuess" class="com.flydean.beans.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
Spring內置了很多預定義變量,如SystemProperties, 你可以像下面這樣直接引用它:
<bean id="taxCalculator" class="com.flydean.beans.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
同樣的,你還可以按名稱引用其他bean屬性,如下示例所示:
<bean id="shapeGuess" class="com.flydean.beans.NumberGuess">
<property name="randomNumber" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
注解配置
要指定默認值,可以將@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;
}
}
下面的示例顯示了在屬性設置器方法上的示例:
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;
}
}
autowired方法和構造函數也可以使用@value注解,如下示例所示:
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;
}
// ...
}
求值
雖然SpEL通常用在Spring的XML和注解中,但是它可以脫離Spring獨立使用的,這時候需要自己去創建一些引導基礎結構類,如解析器。 大多數Spring用戶不需要處理這個基礎結構,只需要編寫表達式字符串進行求值。
支持的功能
SpELl支持很多種功能,包括:
- 文字表達式
- 屬性、數組、列表、映射和索引器
- 內聯 List
- 內聯 Map
- Array
- 方法
- Operators
- 類型
- Constructors
- 變量
- 功能
- bean引用
- 三元運算符(if-then-else)
- elvis
- Safe Navigation Operator
下面分別舉例子:
文字表達式
支持的文本表達式類型包括字符串、數值(int、real、hex)、布爾值和null。字符串由單引號分隔。要將單引號本身放入字符串中,請使用兩個單引號字符。
public class LiteralApp {
public static void main(String[] args) {
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();
}
}
Properties, Arrays, Lists, Maps, and Indexers
Properties 通過 “.” 來訪問嵌套的屬性值。如下:
// 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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Inventions Array
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Members List
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
context, ieee, String.class);
映射的內容是通過在括號內指定文本鍵值獲得的:
// 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");
Inline List
你可以直接在表達式中表示列表:
// 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);
Inline Map
你還可以使用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);
構造數組
可以使用熟悉的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);
方法
可以通過使用典型的Java編程語法來調用方法。還可以對文本調用方法。還支持變量參數。以下示例演示如何調用方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
類型
您可以使用特殊的T運算符來指定java.lang.class(類型)的實例。靜態方法也可以使用此運算符調用。StandardEvaluationContext使用TypeLocator來查找類型,StandardTypeLocator(可以替換)是在理解java.lang包的基礎上構建的。這意味着T()對java.lang中類型的引用不需要完全限定,但所有其他類型引用都必須是限定的。下面的示例演示如何使用T運算符:
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);
構造器
可以使用new運算符調用構造函數。除了基元類型(int、float等)和字符串之外,其他類型都應該使用完全限定的類名。下面的示例演示如何使用新的運算符來調用構造函數:
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);
變量
可以使用#variableName語法引用表達式中的變量。變量是通過在EvaluationContext實現上使用setVariable方法設置的。以下示例顯示如何使用變量:
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
#this和#root
#this始終是定義的,並引用當前的評估對象。#root變量總是被定義並引用根上下文對象。盡管#this可能會隨着表達式的組件的計算而變化,但是#root始終引用根。以下示例說明如何使用#this和#root變量:
// 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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);
函數
您可以通過注冊可以在表達式字符串中調用的用戶定義函數來擴展spel。該函數通過EvaluationContext注冊。以下示例顯示如何注冊用戶定義函數:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
Bean引用
如果已使用bean resolver配置了評估上下文,則可以使用@符號從表達式中查找bean。以下示例顯示了如何執行此操作:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").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);
If-Then-Else
可以使用三元運算符在表達式中執行if-then-else條件邏輯。下面的列表顯示了一個最小的示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
Elvis
ELVIS運算符是三元運算符語法的縮寫,在groovy語言中使用。對於三元運算符語法,通常必須重復變量兩次,如下示例所示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,您可以使用Elvis操作符(以Elvis的發型命名)。下面的示例演示如何使用Elvis運算符:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
Safe Navigation 運算符
Safe Navigation操作符用於避免nullpointerException,它來自groovy語言。通常,當您引用一個對象時,您可能需要在訪問該對象的方法或屬性之前驗證它不是空的。為了避免這種情況,Safe Navigation操作符返回空值而不是拋出異常。以下示例說明如何使用Safe Navigation:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
集合選擇
Selection是一種功能強大的表達式語言功能,通過從源集合的條目中進行選擇,可以將源集合轉換為另一個集合。
Selection使用的語法為.?[selectionExpression]。它過濾集合並返回包含原始元素子集的新集合。例如,selection可以讓我們很容易地獲得塞爾維亞發明家的列表,如下示例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
在list和map上都可以Selection。對於list,將根據每個單獨的列表元素評估選擇條件。針對map,選擇標准針對每個映射條目(Java類型Map.Entry)進行評估。每個map項都有其鍵和值,可以作為屬性訪問,以便在選擇中使用。
以下表達式返回一個新map,該映射由原始map的那些元素組成,其中輸入值小於27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回所有選定的元素之外,您還能檢索第一個或最后一個值。要獲取與所選內容匹配的第一個條目,語法為。.^ [selectionExpression]。要獲取最后一個匹配的選擇,語法為.$[SelectionExpression]。
集合投影
Projection允許集合驅動子表達式的計算,結果是一個新集合。投影的語法是.![projectionExpression]。例如,假設我們有一個發明家列表,但是想要他們出生的城市列表。實際上,我們想為發明家列表中的每個條目評估“placeofbirth.city”。下面的示例使用投影進行此操作:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
您還可以使用map來驅動投影,在這種情況下,投影表達式針對map中的每個條目(表示為Java Map.Entry)進行評估。跨map投影的結果是一個列表,其中包含對每個map條目的投影表達式的計算。
表達式模板化
表達式模板允許將文本與一個或多個計算塊混合。每個評估塊都由您可以定義的前綴和后綴字符分隔。常見的選擇是使用#{ }作為分隔符,如下示例所示:
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
字符串的計算方法是將文本“random number is”與計算#{ }分隔符內表達式的結果(在本例中,是調用該random()方法的結果)連接起來。parseExpression()方法的第二個參數的類型為parserContext。ParserContext接口用於影響表達式的解析方式,以支持表達式模板化功能。TemplateParserContext的定義如下:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
本節的例子可以參考spel
更多教程請參考 flydean的博客