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代碼:
1
2
3
4
5
6
7
|
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代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@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代碼:
1
2
3
4
5
6
7
8
|
@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代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@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代碼:
1
2
3
4
5
6
7
8
9
10
11
|
@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代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@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代碼:
1
2
3
4
5
6
7
8
|
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代碼:
1
2
3
4
|
//2.安全訪問
context.setRootObject( null );
Object
result3 = parser.parseExpression( "#root?.year" ).getValue(context,
Object. class );
Assert.assertEquals( null ,
result3); |
SpEL引入了Groovy的安全導航運算符,比如此處根對象爲null,所以如果訪問其屬性時肯定拋出空指針異常,而採用“?.”安全訪問導航運算符將不拋空指針異常,而是簡單的返回null。
java代碼:
1
2
3
4
5
6
7
|
//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代碼:
1
2
3
4
|
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代碼:
1
2
3
4
5
6
7
8
9
10
|
@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代碼:
1
2
|
//將返回不可修改的空List
List<Integer>
result2 = parser.parseExpression( "{}" ).getValue(List. class ); |
java代碼:
1
2
3
4
5
6
7
8
9
|
//對於字面量列表也將返回不可修改的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代碼:
1
2
3
4
5
6
|
//對於列表中只要有一個不是字面量表達式,將只返回原始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代碼:
1
2
|
//聲明一個大小爲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代碼:
1
2
|
//定義多維數組但不初始化
int [][][]
result3 = parser.parseExpression( "new
int[1][2][3]" ).getValue( int [][][]. class ); |
java代碼:
1
2
3
4
5
6
7
|
//錯誤的定義多維數組,多維數組不能初始化
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代碼:
1
2
3
4
|
//SpEL內聯List訪問
int
result1 = parser.parseExpression( "{1,2,3}[0]" ).getValue( int . class );
//即list.get(0)
Assert.assertEquals( 1 ,
result1); |
java代碼:
1
2
3
4
5
6
7
8
9
|
//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代碼:
1
2
3
4
5
6
7
|
//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
2
3
4
5
6
|
//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代碼:
1
2
3
4
5
6
7
8
9
10
11
|
//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代碼:
1
2
3
4
5
6
7
|
//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
2
3
4
5
|
//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代碼:
1
2
3
4
5
6
7
|
//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代碼:
1
2
3
4
5
6
|
//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
2
3
4
5
|
//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代碼:
1
2
3
4
5
6
7
|
//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代碼:
1
2
3
4
5
6
7
8
9
|
//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表示自定義變量,需要在上下文定義。