轉自:https://blog.csdn.net/u011225629/article/details/47143083
5.1 概述
5.1.1 概述
Spring表達式語言全稱為“Spring Expression Language”,縮寫為“SpEL”,類似於Struts2x中使用的OGNL表達式語言,能在運行時構建復雜表達式、存取對象圖屬性、對象方法調用等等,並且能與Spring功能完美整合,如能用來配置Bean定義。
表達式語言給靜態Java語言增加了動態功能。
SpEL是單獨模塊,只依賴於core模塊,不依賴於其他模塊,可以單獨使用。
5.1.2 能干什么
表達式語言一般是用最簡單的形式完成最主要的工作,減少我們的工作量。
SpEL支持如下表達式:
一、基本表達式:字面量表達式、關系,邏輯與算數運算表達式、字符串連接及截取表達式、三目運算及Elivis表達式、正則表達式、括號優先級表達式;
二、類相關表達式:類類型表達式、類實例化、instanceof表達式、變量定義及引用、賦值表達式、自定義函數、對象屬性存取及安全導航表達式、對象方法調用、Bean引用;
三、集合相關表達式:內聯List、內聯數組、集合,字典訪問、列表,字典,數組修改、集合投影、集合選擇;不支持多維內聯數組初始化;不支持內聯字典定義;
四、其他表達式:模板表達式。
注:SpEL表達式中的關鍵字是不區分大小寫的。
5.2 SpEL基礎
5.2.1 HelloWorld
首先准備支持SpEL的Jar包:“org.springframework.expression-3.0.5.RELEASE.jar”將其添加到類路徑中。
SpEL在求表達式值時一般分為四步,其中第三步可選:首先構造一個解析器,其次解析器解析字符串表達式,在此構造上下文,最后根據上下文得到表達式運算后的值。
讓我們看下代碼片段吧:
java代碼:
Java代碼
package cn.javass.spring.chapter5;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELTest {
@Test
public void helloWorld() {
ExpressionParser parser = new SpelExpressionParser();
Expression expression =
parser.parseExpression("('Hello' + ' World').concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
Assert.assertEquals("Hello World!", expression.getValue(context));
}
}
接下來讓我們分析下代碼:
1)創建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默認實現;
2)解析表達式:使用ExpressionParser的parseExpression來解析相應的表達式為Expression對象。
3)構造上下文:准備比如變量定義等等表達式需要的上下文數據。
4)求值:通過Expression接口的getValue方法根據上下文獲得表達式值。
是不是很簡單,接下來讓我們看下其具體實現及原理吧。
5.2.3 SpEL原理及接口
SpEL提供簡單的接口從而簡化用戶使用,在介紹原理前讓我們學習下幾個概念:
一、表達式:表達式是表達式語言的核心,所以表達式語言都是圍繞表達式進行的,從我們角度來看是“干什么”;
二、解析器:用於將字符串表達式解析為表達式對象,從我們角度來看是“誰來干”;
三、上下文:表達式對象執行的環境,該環境可能定義變量、定義自定義函數、提供類型轉換等等,從我們角度看是“在哪干”;
四、根對象及活動上下文對象:根對象是默認的活動上下文對象,活動上下文對象表示了當前表達式操作的對象,從我們角度看是“對誰干”。
理解了這些概念后,讓我們看下SpEL如何工作的呢,如圖5-1所示:
圖5-1 工作原理
1)首先定義表達式:“1+2”;
2)定義解析器ExpressionParser實現,SpEL提供默認實現SpelExpressionParser;
2.1)SpelExpressionParser解析器內部使用Tokenizer類進行詞法分析,即把字符串流分析為記號流,記號在SpEL使用Token類來表示;
2.2)有了記號流后,解析器便可根據記號流生成內部抽象語法樹;在SpEL中語法樹節點由SpelNode接口實現代表:如OpPlus表示加操作節點、IntLiteral表示int型字面量節點;使用SpelNodel實現組成了抽象語法樹;
2.3)對外提供Expression接口來簡化表示抽象語法樹,從而隱藏內部實現細節,並提供getValue簡單方法用於獲取表達式值;SpEL提供默認實現為SpelExpression;
3)定義表達式上下文對象(可選),SpEL使用EvaluationContext接口表示上下文對象,用於設置根對象、自定義變量、自定義函數、類型轉換器等,SpEL提供默認實現StandardEvaluationContext;
4)使用表達式對象根據上下文對象(可選)求值(調用表達式對象的getValue方法)獲得結果。
接下來讓我們看下SpEL的主要接口吧:
1)ExpressionParser接口:表示解析器,默認實現是org.springframework.expression.spel.standard包中的SpelExpressionParser類,使用parseExpression方法將字符串表達式轉換為Expression對象,對於ParserContext接口用於定義字符串表達式是不是模板,及模板開始與結束字符:
java代碼:
Java代碼
public interface ExpressionParser {
Expression parseExpression(String expressionString);
Expression parseExpression(String expressionString, ParserContext context);
}
來看下示例:
java代碼:
Java代碼
@Test
public void testParserContext() {
ExpressionParser parser = new SpelExpressionParser();
ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
};
String template = "#{'Hello '}#{'World!'}";
Expression expression = parser.parseExpression(template, parserContext);
Assert.assertEquals("Hello World!", expression.getValue());
}
在此我們演示的是使用ParserContext的情況,此處定義了ParserContext實現:定義表達式是模塊,表達式前綴為“#{”,后綴為“}”;使用parseExpression解析時傳入的模板必須以“#{”開頭,以“}”結尾,如"#{'Hello '}#{'World!'}"。
默認傳入的字符串表達式不是模板形式,如之前演示的Hello World。
2)EvaluationContext接口:表示上下文環境,默認實現是org.springframework.expression.spel.support包中的StandardEvaluationContext類,使用setRootObject方法來設置根對象,使用setVariable方法來注冊自定義變量,使用registerFunction來注冊自定義函數等等。
3)Expression接口:表示表達式對象,默認實現是org.springframework.expression.spel.standard包中的SpelExpression,提供getValue方法用於獲取表達式值,提供setValue方法用於設置對象值。
了解了SpEL原理及接口,接下來的事情就是SpEL語法了。
5.3 SpEL語法
5.3.1 基本表達式
一、字面量表達式: SpEL支持的字面量包括:字符串、數字類型(int、long、float、double)、布爾類型、null類型。
類型
示例
字符串
String str1 = parser.parseExpression("'Hello World!'").getValue(String.class);
String str2 = parser.parseExpression("\"Hello World!\"").getValue(String.class);
數字類型
int int1 = parser.parseExpression("1").getValue(Integer.class);
long long1 = parser.parseExpression("-1L").getValue(long.class);
float float1 = parser.parseExpression("1.1").getValue(Float.class);
double double1 = parser.parseExpression("1.1E+2").getValue(double.class);
int hex1 = parser.parseExpression("0xa").getValue(Integer.class);
long hex2 = parser.parseExpression("0xaL").getValue(long.class);
布爾類型
boolean true1 = parser.parseExpression("true").getValue(boolean.class);
boolean false1 = parser.parseExpression("false").getValue(boolean.class);
null類型
Object null1 = parser.parseExpression("null").getValue(Object.class);
二、算數運算表達式: SpEL支持加(+)、減(-)、乘(*)、除(/)、求余(%)、冪(^)運算。
類型
示例
加減乘除
int result1 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);//-3
求余
int result2 = parser.parseExpression("4%3").getValue(Integer.class);//1
冪運算
int result3 = parser.parseExpression("2^3").getValue(Integer.class);//8
SpEL還提供求余(MOD)和除(DIV)而外兩個運算符,與“%”和“/”等價,不區分大小寫。
三、關系表達式:等於(==)、不等於(!=)、大於(>)、大於等於(>=)、小於(<)、小於等於(<=),區間(between)運算,如“parser.parseExpression("1>2").getValue(boolean.class);”將返回false;而“parser.parseExpression("1 between {1, 2}").getValue(boolean.class);”將返回true。
between運算符右邊操作數必須是列表類型,且只能包含2個元素。第一個元素為開始,第二個元素為結束,區間運算是包含邊界值的,即 xxx>=list.get(0) && xxx<=list.get(1)。
SpEL同樣提供了等價的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”來表示等於、不等於、大於、大於等於、小於、小於等於,不區分大小寫。
四、邏輯表達式:且(and)、或(or)、非(!或NOT)。
java代碼:
查看復制到剪貼板打印
String expression1 = "2>1 and (!true or !false)";
boolean result1 = parser.parseExpression(expression1).getValue(boolean.class);
Assert.assertEquals(true, result1);
String expression2 = "2>1 and (NOT true or NOT false)";
boolean result2 = parser.parseExpression(expression2).getValue(boolean.class);
Assert.assertEquals(true, result2);
注:邏輯運算符不支持 Java中的 && 和 || 。
五、字符串連接及截取表達式:使用“+”進行字符串連接,使用“'String'[0] [index]”來截取一個字符,目前只支持截取一個,如“'Hello ' + 'World!'”得到“Hello World!”;而“'Hello World!'[0]”將返回“H”。
六、三目運算及Elivis運算表達式:
三目運算符 “表達式1?表達式2:表達式3”用於構造三目運算表達式,如“2>1?true:false”將返回true;
Elivis運算符“表達式1?:表達式2”從Groovy語言引入用於簡化三目運算符的,當表達式1為非null時則返回表達式1,當表達式1為null時則返回表達式2,簡化了三目運算符方式“表達式1? 表達式1:表達式2”,如“null?:false”將返回false,而“true?:false”將返回true;
七、正則表達式:使用“str matches regex,如“'123' matches '\\d{3}'”將返回true;
八、括號優先級表達式:使用“(表達式)”構造,括號里的具有高優先級。
5.3.3 類相關表達式
一、類類型表達式:使用“T(Type)”來表示java.lang.Class實例,“Type”必須是類全限定名,“java.lang”包除外,即該包下的類可以不指定包名;使用類類型表達式還可以進行訪問類靜態方法及類靜態字段。
具體使用方法如下:
java代碼:
查看復制到剪貼板打印
@Test
public void testClassTypeExpression() {
ExpressionParser parser = new SpelExpressionParser();
//java.lang包類訪問
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
Assert.assertEquals(String.class, result1);
//其他包類訪問
String expression2 = "T(cn.javass.spring.chapter5.SpELTest)";
Class<String> result2 = parser.parseExpression(expression2).getValue(Class.class); Assert.assertEquals(SpELTest.class, result2);
//類靜態字段訪問
int result3=parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
Assert.assertEquals(Integer.MAX_VALUE, result3);
//類靜態方法調用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
Assert.assertEquals(1, result4);
}
對於java.lang包里的可以直接使用“T(String)”訪問;其他包必須是類全限定名;可以進行靜態字段訪問如“T(Integer).MAX_VALUE”;也可以進行靜態方法訪問如“T(Integer).parseInt('1')”。
二、類實例化:類實例化同樣使用java關鍵字“new”,類名必須是全限定名,但java.lang包內的類型除外,如String、Integer。
java代碼:
查看復制到剪貼板打印
@Test
public void testConstructorExpression() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression("new String('haha')").getValue(String.class);
Assert.assertEquals("haha", result1);
Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
Assert.assertNotNull(result2);
}
實例化完全跟Java內方式一樣。
三、instanceof表達式:SpEL支持instanceof運算符,跟Java內使用同義;如“'haha' instanceof T(String)”將返回true。
四、變量定義及引用:變量定義通過EvaluationContext接口的setVariable(variableName, value)方法定義;在表達式中使用“#variableName”引用;除了引用自定義變量,SpE還允許引用根對象及當前上下文對象,使用“#root”引用根對象,使用“#this”引用當前上下文對象;
java代碼:
查看復制到剪貼板打印
@Test
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("variable", "haha");
context.setVariable("variable", "haha");
String result1 = parser.parseExpression("#variable").getValue(context, String.class);
Assert.assertEquals("haha", result1);
context = new StandardEvaluationContext("haha");
String result2 = parser.parseExpression("#root").getValue(context, String.class);
Assert.assertEquals("haha", result2);
String result3 = parser.parseExpression("#this").getValue(context, String.class);
Assert.assertEquals("haha", result3);
}
使用“#variable”來引用在EvaluationContext定義的變量;除了可以引用自定義變量,還可以使用“#root”引用根對象,“#this”引用當前上下文對象,此處“#this”即根對象。
五、自定義函數:目前只支持類靜態方法注冊為自定義函數;SpEL使用StandardEvaluationContext的registerFunction方法進行注冊自定義函數,其實完全可以使用setVariable代替,兩者其實本質是一樣的;
java代碼:
查看復制到剪貼板打印
@Test
public void testFunctionExpression() throws SecurityException, NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
context.registerFunction("parseInt", parseInt);
context.setVariable("parseInt2", parseInt);
String expression1 = "#parseInt('3') == #parseInt2('3')";
boolean result1 = parser.parseExpression(expression1).getValue(context, boolean.class);
Assert.assertEquals(true, result1);
}
此處可以看出“registerFunction”和“setVariable”都可以注冊自定義函數,但是兩個方法的含義不一樣,推薦使用“registerFunction”方法注冊自定義函數。
六、賦值表達式:SpEL即允許給自定義變量賦值,也允許給跟對象賦值,直接使用“#variableName=value”即可賦值:
java代碼:
查看復制到剪貼板打印
@Test
public void testAssignExpression() {
ExpressionParser parser = new SpelExpressionParser();
//1.給root對象賦值
EvaluationContext context = new StandardEvaluationContext("aaaa");
String result1 = parser.parseExpression("#root='aaaaa'").getValue(context, String.class);
Assert.assertEquals("aaaaa", result1);
String result2 = parser.parseExpression("#this='aaaa'").getValue(context, String.class);
Assert.assertEquals("aaaa", result2);
//2.給自定義變量賦值
context.setVariable("#variable", "variable");
String result3 = parser.parseExpression("#variable=#root").getValue(context, String.class);
Assert.assertEquals("aaaa", result3);
}
使用“#root='aaaaa'”給根對象賦值,使用“"#this='aaaa'”給當前上下文對象賦值,使用“#variable=#root”給自定義變量賦值,很簡單。
七、對象屬性存取及安全導航表達式:對象屬性獲取非常簡單,即使用如“a.property.property”這種點綴式獲取,SpEL對於屬性名首字母是不區分大小寫的;SpEL還引入了Groovy語言中的安全導航運算符“(對象|屬性)?.屬性”,用來避免但“?.”前邊的表達式為null時拋出空指針異常,而是返回null;修改對象屬性值則可以通過賦值表達式或Expression接口的setValue方法修改。
java代碼:
查看復制到剪貼板打印
ExpressionParser parser = new SpelExpressionParser();
//1.訪問root對象屬性
Date date = new Date();
StandardEvaluationContext context = new StandardEvaluationContext(date);
int result1 = parser.parseExpression("Year").getValue(context, int.class);
Assert.assertEquals(date.getYear(), result1);
int result2 = parser.parseExpression("year").getValue(context, int.class);
Assert.assertEquals(date.getYear(), result2);
對於當前上下文對象屬性及方法訪問,可以直接使用屬性或方法名訪問,比如此處根對象date屬性“year”,注意此處屬性名首字母不區分大小寫。
java代碼:
查看復制到剪貼板打印
//2.安全訪問
context.setRootObject(null);
Object result3 = parser.parseExpression("#root?.year").getValue(context, Object.class);
Assert.assertEquals(null, result3);
SpEL引入了Groovy的安全導航運算符,比如此處根對象為null,所以如果訪問其屬性時肯定拋出空指針異常,而采用“?.”安全訪問導航運算符將不拋空指針異常,而是簡單的返回null。
java代碼:
查看復制到剪貼板打印
//3.給root對象屬性賦值
context.setRootObject(date);
int result4 = parser.parseExpression("Year = 4").getValue(context, int.class);
Assert.assertEquals(4, result4);
parser.parseExpression("Year").setValue(context, 5);
int result5 = parser.parseExpression("Year").getValue(context, int.class);
Assert.assertEquals(5, result5);
給對象屬性賦值可以采用賦值表達式或Expression接口的setValue方法賦值,而且也可以采用點綴方式賦值。
八、對象方法調用:對象方法調用更簡單,跟Java語法一樣;如“'haha'.substring(2,4)”將返回“ha”;而對於根對象可以直接調用方法;
java代碼:
查看復制到剪貼板打印
Date date = new Date();
StandardEvaluationContext context = new StandardEvaluationContext(date);
int result2 = parser.parseExpression("getYear()").getValue(context, int.class);
Assert.assertEquals(date.getYear(), result2);
比如根對象date方法“getYear”可以直接調用。
九、Bean引用:SpEL支持使用“@”符號來引用Bean,在引用Bean時需要使用BeanResolver接口實現來查找Bean,Spring提供BeanFactoryResolver實現;
java代碼:
查看復制到剪貼板打印
@Test
public void testBeanExpression() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.refresh();
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(ctx));
Properties result1 = parser.parseExpression("@systemProperties").getValue(context, Properties.class);
Assert.assertEquals(System.getProperties(), result1);
}
在示例中我們首先初始化了一個IoC容器,ClassPathXmlApplicationContext 實現默認會把“System.getProperties()”注冊為“systemProperties”Bean,因此我們使用 “@systemProperties”來引用該Bean。
5.3.3 集合相關表達式
一、內聯List:從Spring3.0.4開始支持內聯List,使用{表達式,……}定義內聯List,如“{1,2,3}”將返回一個整型的ArrayList,而“{}”將返回空的List,對於字面量表達式列表,SpEL會使用java.util.Collections.unmodifiableList方法將列表設置為不可修改。
java代碼:
查看復制到剪貼板打印
//將返回不可修改的空List
List<Integer> result2 = parser.parseExpression("{}").getValue(List.class);
java代碼:
查看復制到剪貼板打印
//對於字面量列表也將返回不可修改的List
List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);
Assert.assertEquals(new Integer(1), result1.get(0));
try {
result1.set(0, 2);
//不可能執行到這,對於字面量列表不可修改
Assert.fail();
} catch (Exception e) {
}
java代碼:
查看復制到剪貼板打印
//對於列表中只要有一個不是字面量表達式,將只返回原始List,
//不會進行不可修改處理
String expression3 = "{{1+2,2+4},{3,4+4}}";
List<List<Integer>> result3 = parser.parseExpression(expression3).getValue(List.class);
result3.get(0).set(0, 1);
Assert.assertEquals(2, result3.size());
java代碼:
查看復制到剪貼板打印
//聲明一個大小為2的一維數組並初始化
int[] result2 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class);
java代碼:
查看復制到剪貼板打印
//定義一維數組但不初始化
int[] result1 = parser.parseExpression("new int[1]").getValue(int[].class);
二、內聯數組:和Java 數組定義類似,只是在定義時進行多維數組初始化。
java代碼:
查看復制到剪貼板打印
//定義多維數組但不初始化
int[][][] result3 = parser.parseExpression("new int[1][2][3]").getValue(int[][][].class);
java代碼:
查看復制到剪貼板打印
//錯誤的定義多維數組,多維數組不能初始化
String expression4 = "new int[1][2][3]{{1}{2}{3}}";
try {
int[][][] result4 = parser.parseExpression(expression4).getValue(int[][][].class);
Assert.fail();
} catch (Exception e) {
}
三、集合,字典元素訪問:SpEL目前支持所有集合類型和字典類型的元素訪問,使用“集合[索引]”訪問集合元素,使用“map[key]”訪問字典元素;
java代碼:
查看復制到剪貼板打印
//SpEL內聯List訪問
int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
//即list.get(0)
Assert.assertEquals(1, result1);
java代碼:
查看復制到剪貼板打印
//SpEL目前支持所有集合類型的訪問
Collection<Integer> collection = new HashSet<Integer>();
collection.add(1);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("collection", collection);
int result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
//對於任何集合類型通過Iterator來定位元素
Assert.assertEquals(2, result2);
java代碼:
查看復制到剪貼板打印
//SpEL對Map字典元素訪問的支持
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable("map", map);
int result3 = parser.parseExpression("#map['a']").getValue(context3, int.class);
Assert.assertEquals(1, result3);
注:集合元素訪問是通過Iterator遍歷來定位元素位置的。
四、列表,字典,數組元素修改:可以使用賦值表達式或Expression接口的setValue方法修改;
java代碼:
查看復制到剪貼板打印
//1.修改數組元素值
int[] array = new int[] {1, 2};
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("array", array);
int result1 = parser.parseExpression("#array[1] = 3").getValue(context1, int.class);
Assert.assertEquals(3, result1);
java代碼:
查看復制到剪貼板打印
//2.修改集合值
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(1);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("collection", collection);
int result2 = parser.parseExpression("#collection[1] = 3").getValue(context2, int.class);
Assert.assertEquals(3, result2);
parser.parseExpression("#collection[1]").setValue(context2, 4);
result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);
Assert.assertEquals(4, result2);
java代碼:
查看復制到剪貼板打印
//3.修改map元素值
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable("map", map);
int result3 = parser.parseExpression("#map['a'] = 2").getValue(context3, int.class);
Assert.assertEquals(2, result3);
對數組修改直接對“#array[index]”賦值即可修改元素值,同理適用於集合和字典類型。
五、集合投影:在SQL中投影指從表中選擇出列,而在SpEL指根據集合中的元素中通過選擇來構造另一個集合,該集合和原集合具有相同數量的元素;SpEL使用“(list|map).![投影表達式]”來進行投影運算:
java代碼:
查看復制到剪貼板打印
//1.首先准備測試數據
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(4); collection.add(5);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1); map.put("b", 2);
java代碼:
查看復制到剪貼板打印
//2.測試集合或數組
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("collection", collection);
Collection<Integer> result1 =
parser.parseExpression("#collection.![#this+1]").getValue(context1, Collection.class);
Assert.assertEquals(2, result1.size());
Assert.assertEquals(new Integer(5), result1.iterator().next());
對於集合或數組使用如上表達式進行投影運算,其中投影表達式中“#this”代表每個集合或數組元素,可以使用比如“#this.property”來獲取集合元素的屬性,其中“#this”可以省略。
java代碼:
查看復制到剪貼板打印
//3.測試字典
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
List<Integer> result2 =
parser.parseExpression("#map.![ value+1]").getValue(context2, List.class);
Assert.assertEquals(2, result2.size());
SpEL投影運算還支持Map投影,但Map投影最終只能得到List結果,如上所示,對於投影表達式中的“#this”將是Map.Entry,所以可以使用“value”來獲取值,使用“key”來獲取鍵。
六、集合選擇:在SQL中指使用select進行選擇行數據,而在SpEL指根據原集合通過條件表達式選擇出滿足條件的元素並構造為新的集合,SpEL使用“(list|map).?[選擇表達式]”,其中選擇表達式結果必須是boolean類型,如果true則選擇的元素將添加到新集合中,false將不添加到新集合中。
java代碼:
查看復制到剪貼板打印
//1.首先准備測試數據
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(4); collection.add(5);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1); map.put("b", 2);
java代碼:
查看復制到剪貼板打印
//2.集合或數組測試
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable("collection", collection);
Collection<Integer> result1 =
parser.parseExpression("#collection.?[#this>4]").getValue(context1, Collection.class);
Assert.assertEquals(1, result1.size());
Assert.assertEquals(new Integer(5), result1.iterator().next());
對於集合或數組選擇,如“#collection.?[#this>4]”將選擇出集合元素值大於4的所有元素。選擇表達式必須返回布爾類型,使用“#this”表示當前元素。
java代碼:
查看復制到剪貼板打印
//3.字典測試
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable("map", map);
Map<String, Integer> result2 =
parser.parseExpression("#map.?[#this.key != 'a']").getValue(context2, Map.class);
Assert.assertEquals(1, result2.size());
List<Integer> result3 =
parser.parseExpression("#map.?[key != 'a'].![value+1]").getValue(context2, List.class);
Assert.assertEquals(new Integer(3), result3.iterator().next());
對於字典選擇,如“#map.?[#this.key != 'a']”將選擇鍵值不等於”a”的,其中選擇表達式中“#this”是Map.Entry類型,而最終結果還是Map,這點和投影不同;集合選擇和投影可以一起使用,如“#map.?[key != 'a'].![value+1]”將首先選擇鍵值不等於”a”的,然后在選出的Map中再進行“value+1”的投影。
5.3.4 表達式模板
模板表達式就是由字面量與一個或多個表達式塊組成。每個表達式塊由“前綴+表達式+后綴”形式組成,如“${1+2}”即表達式塊。在前邊我們已經介紹了使用ParserContext接口實現來定義表達式是否是模板及前綴和后綴定義。在此就不多介紹了,如“Error ${#v0} ${#v1}”表達式表示由字面量“Error ”、模板表達式“#v0”、模板表達式“#v1”組成,其中v0和v1表示自定義變量,需要在上下文定義。
5.4.1 xml風格的配置
SpEL支持在Bean定義時注入,默認使用“#{SpEL表達式}”表示,其中“#root”根對象默認可以認為是ApplicationContext,只有ApplicationContext實現默認支持SpEL,獲取根對象屬性其實是獲取容器中的Bean。
首先看下配置方式(chapter5/el1.xml)吧:
java代碼:
查看復制到剪貼板打印
<bean id="world" class="java.lang.String">
<constructor-arg value="#{' World!'}"/>
</bean>
<bean id="hello1" class="java.lang.String">
<constructor-arg value="#{'Hello'}#{world}"/>
</bean>
<bean id="hello2" class="java.lang.String">
<constructor-arg value="#{'Hello' + world}"/>
<!-- 不支持嵌套的 -->
<!--<constructor-arg value="#{'Hello'#{world}}"/>-->
</bean>
<bean id="hello3" class="java.lang.String">
<constructor-arg value="#{'Hello' + @world}"/>
</bean>
模板默認以前綴“#{”開頭,以后綴“}”結尾,且不允許嵌套,如“#{'Hello'#{world}}”錯誤,如“#{'Hello' + world}”中“world”默認解析為Bean。當然可以使用“@bean”引用了。
接下來測試一下吧:
java代碼:
查看復制到剪貼板打印
@Test
public void testXmlExpression() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter5/el1.xml");
String hello1 = ctx.getBean("hello1", String.class);
String hello2 = ctx.getBean("hello2", String.class);
String hello3 = ctx.getBean("hello3", String.class);
Assert.assertEquals("Hello World!", hello1);
Assert.assertEquals("Hello World!", hello2);
Assert.assertEquals("Hello World!", hello3);
}
是不是很簡單,除了XML配置方式,Spring還提供一種注解方式@Value,接着往下看吧。
5.4.2 注解風格的配置
基於注解風格的SpEL配置也非常簡單,使用@Value注解來指定SpEL表達式,該注解可以放到字段、方法及方法參數上。
測試Bean類如下,使用@Value來指定SpEL表達式:
java代碼:
查看復制到剪貼板打印
package cn.javass.spring.chapter5;
import org.springframework.beans.factory.annotation.Value;
public class SpELBean {
@Value("#{'Hello' + world}")
private String value;
//setter和getter由於篇幅省略,自己寫上
}
首先看下配置文件(chapter5/el2.xml):
java代碼:
查看復制到剪貼板打印
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="world" class="java.lang.String">
<constructor-arg value="#{' World!'}"/>
</bean>
<bean id="helloBean1" class="cn.javass.spring.chapter5.SpELBean"/>
<bean id="helloBean2" class="cn.javass.spring.chapter5.SpELBean">
<property name="value" value="haha"/>
</bean>
</beans>
配置時必須使用“<context:annotation-config/>”來開啟對注解的支持。
有了配置文件那開始測試吧:
java代碼:
查看復制到剪貼板打印
@Test
public void testAnnotationExpression() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter5/el2.xml");
SpELBean helloBean1 = ctx.getBean("helloBean1", SpELBean.class);
Assert.assertEquals("Hello World!", helloBean1.getValue());
SpELBean helloBean2 = ctx.getBean("helloBean2", SpELBean.class);
Assert.assertEquals("haha", helloBean2.getValue());
}
其中“helloBean1 ”值是SpEL表達式的值,而“helloBean2”是通過setter注入的值,這說明setter注入將覆蓋@Value的值。
5.4.3 在Bean定義中SpEL的問題
如果有同學問“#{我不是SpEL表達式}”不是SpEL表達式,而是公司內部的模板,想換個前綴和后綴該如何實現呢?
那我們來看下Spring如何在IoC容器內使用BeanExpressionResolver接口實現來求值SpEL表達式,那如果我們通過某種方式獲取該接口實現,然后把前綴后綴修改了不就可以了。
此處我們使用BeanFactoryPostProcessor接口提供postProcessBeanFactory回調方法,它是在IoC容器創建好但還未進行任何Bean初始化時被ApplicationContext實現調用,因此在這個階段把SpEL前綴及后綴修改掉是安全的,具體代碼如下:
java代碼:
查看復制到剪貼板打印
package cn.javass.spring.chapter5;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.expression.StandardBeanExpressionResolver;
public class SpELBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
StandardBeanExpressionResolver resolver = (StandardBeanExpressionResolver) beanFactory.getBeanExpressionResolver();
resolver.setExpressionPrefix("%{");
resolver.setExpressionSuffix("}");
}
}
首先通過 ConfigurableListableBeanFactory的getBeanExpressionResolver方法獲取BeanExpressionResolver實現,其次強制類型轉換為StandardBeanExpressionResolver,其為Spring默認實現,然后改掉前綴及后綴。
開始測試吧,首先准備配置文件(chapter5/el3.xml):
java代碼:
查看復制到剪貼板打印
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean class="cn.javass.spring.chapter5.SpELBeanFactoryPostProcessor"/>
<bean id="world" class="java.lang.String">
<constructor-arg value="%{' World!'}"/>
</bean>
<bean id="helloBean1" class="cn.javass.spring.chapter5.SpELBean"/>
<bean id="helloBean2" class="cn.javass.spring.chapter5.SpELBean">
<property name="value" value="%{'Hello' + world}"/>
</bean>
</beans>
配置文件和注解風格的幾乎一樣,只有SpEL表達式前綴變為“%{”了,並且注冊了“cn.javass.spring.chapter5.SpELBeanFactoryPostProcessor”Bean,用於修改前綴和后綴的。
寫測試代碼測試一下吧:
java代碼:
查看復制到剪貼板打印
@Test
public void testPrefixExpression() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter5/el3.xml");
SpELBean helloBean1 = ctx.getBean("helloBean1", SpELBean.class);
Assert.assertEquals("#{'Hello' + world}", helloBean1.getValue());
SpELBean helloBean2 = ctx.getBean("helloBean2", SpELBean.class);
Assert.assertEquals("Hello World!", helloBean2.getValue());
}
此處helloBean1 中通過@Value注入的“#{'Hello' + world}”結果還是“#{'Hello' + world}”說明不對其進行SpEL表達式求值了,而helloBean2使用“%{'Hello' + world}”注入,得到正確的“"Hello World!”。
