7.12.3 SpEL语法详述

7.12.3 SpEL语法详述

虽然SpEL在功能上大致与JSP2EL类似,但SpELJSP 2EL更强大,接下来详细介绍SpEL所支持的各种语法细节。

1. 直接量表达式

直接量表达式是SpEL中最简单的表达式,直接量表达式就是在表达式中使用Java语言支持的直接量,包括字符串日期数值boolean值和null:
例如如下代码片段:

1
2
3
4
Expression exp = parser.parseExpression("'Hello World'");
System.out.println(exp.getValue(String.class));
exp = parser.parseExpression("0.23");
System.out.println(exp.getValue(Double.class));

2. 在表达式中创建数组

SpEL表达式直接支持使用静态初始化动态初始化两种语法来创建数组。例如如下代码片段:

1
2
3
4
5
6
7
// 创建一个数组
Expression exp = parser
.parseExpression("new String[]{'java' , 'Struts' , 'Spring'}");
System.out.println(exp.getValue());
// 创建二维数组
exp = parser.parseExpression("new int[2][4]");
System.out.println(exp.getValue());

3. 在表达式中创建List集合

SpEL直接使用如下语法来创建List集合:

1
{element1,element2,element3,...,}

例如如下代码:

1
2
3
Expression exp=parser.parseExpression("{'java','Struts','Spring'}");
System.out.println(exp.getValue());
exp=parser.parseExpression(""{{'大学','中庸','论语','孟子'},{'诗经',''尚书,'礼记','周易','春秋'}}")

4. 在表达式中访问List、Map等集合元素

为了在SpEL中访问List集合的元素,可以使用如下语法格式

1
List[index]

为了在SpEL中访问Map集合的元素,可以使用如下语法格式:

1
map[key]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> list = new ArrayList<String>();
list.add("Java");
list.add("Spring");
Map<String, Double> map = new HashMap<String, Double>();
map.put("Java", 80.0);
map.put("Spring", 89.0);
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置两个变量
ctx.setVariable("mylist", list);
ctx.setVariable("mymap", map);
// 访问List集合的第二个元素
System.out.println(parser.parseExpression("#mylist[1]").getValue(ctx));
// 访问Map集合的指定元素
System.out.println(
parser.parseExpression("#mymap['Java']").getValue(ctx));

5. 调用方法

SpEL中调用方法与在Java代码中调用方法没有任何区别。如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 调用String对象的substring()方法
System.out.println(parser
.parseExpression("'HelloWorld'.substring(2, 5)").getValue());
List<String> list = new ArrayList<String>();
list.add("java");
list.add("struts");
list.add("spring");
list.add("hibernate");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置一个变量
ctx.setVariable("mylist", list);
// 调用指定变量所代表的对象的subList()方法
System.out.println(
parser.parseExpression("#mylist.subList(1, 3)").getValue(ctx));

6. 算术、比较、逻辑、赋值、三目等运算符

JSP2EL类似的是SpEL同样支持算术、比较、逻辑、赋值、三目运算符等各种运算符,值得指出的是,在SpEL中使用赋值运算符的功能比较强大,这种赋值可以直接改变表达式所引用的实际对象。
如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> list = new ArrayList<String>();
list.add("java");
list.add("struts");
list.add("spring");
list.add("hibernate");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置一个变量
ctx.setVariable("mylist", list);
// 对集合的第一个元素进行赋值
parser.parseExpression("#mylist[0]='疯狂Java讲义'").getValue(ctx);
// 下面将输出 疯狂Java讲义
System.out.println(list.get(0));
// 使用三目运算符
System.out.println(parser
.parseExpression(
"#mylist.size()>3?" + "'myList长度大于3':'myList长度不大于3'")
.getValue(ctx));

运行结果:

1
2
疯狂Java讲义
myList长度大于3

7. 类型运算符

SpEL提供了一个特殊的运算符:T(),这个运算符用于告诉SpEL将该运算符内的字符串当成”类”处理,避兔Spring对其进行其他解析。尤其是调用某个类的静态方法时,T()运算符尤其有用。

1
2
3
4
5
6
7
// 调用Math的静态方法
System.out.println(parser.parseExpression("T(java.lang.Math).random()")
.getValue());
// 调用Math的静态方法
System.out.println(
parser.parseExpression("T(System).getProperty('os.name')")
.getValue());

例如如下代码:

1
2
0.09320472533221835
Windows 10

正如在上面的代码中所看到的,在表达式中使用某个类时,推荐使用该类的全限定类名。但如果只写类名,不写包名,SpEL也可以尝试处理,SpEL使用StandardTypeLocator去定位这些类,它默认会在java.lang包下找这些类。
T()运算符使用java.lang包下的类时可以省略包名,但使用其他包下的所有类时应使用全限定类名

8. 调用构造器

SpEL允许在表达式中直接使用new来调用构造器,这种调用可以创建一个Java对象。例如如下代码

1
2
3
4
5
6
7
8
9
// 创建对象
System.out.println(parser
.parseExpression("new String('HelloWorld').substring(2, 4)")
.getValue());
// 创建对象
System.out.println(parser
.parseExpression(
"new javax.swing.JFrame('测试')" + ".setVisible('true')")
.getValue());

9. 变量

SpEL允许通过EvaluationContext来使用变量,该对象包含了一个setVariable(String name, Object value)方法,该方法用于设置一个变量。一旦在EvaluationContext中设置了变量,就可以在SpEL中通过#name来访问该变量。前面已经有不少在SpEL中使用变量的例子,故此处不再赘述。
值得指出的是,SpEL中有如下两个特殊的变量。

  • #this:引用SpEL当前正在计算的对象。
  • #root:引用SpELEvaluationContextroot对象。

10. 自定义函数

SpEL允许开发者开发自定义函数。类似于JSP 2 EL中的自定义函数,所谓自定义函数,也就是为Java方法重新起个名字而已。
通过StandardEvaluationContext的如下方法即可在SpEL中注册自定义函数。
registerFunction(String name, Method m):将m方法注册成自定义函数,该函数的名称为nameSpEL自定义函数的作用并不大,因为SpEL本身已经允许在表达式语言中调用方法,因此将方法重新定义为自定义函数的意义不大。

11. Elvis运算符

Elvis运算符只是三目运算符的特殊写法,例如对于如下三目运算符写法:

1
name != null ? name : "newValue"

上面的语句使用三目运算符需要将name变量写两次,因此比较烦琐。SpEL允许将上面写法简写为如下形式:

1
name?: "newValue"

12. 安全导航操作

SpEL中使用如下语句时可能导致NullPointerException:

1
foo.bar

如果root对象的foo属性本身已经是null,那么上面表达式尝试访问foo属性的bar属性时自然就会引发异常。
为了避免上面表达式中的NullPointerException异常,SpEL支持如下用法:

1
foo?.bar

上面表达式在计算root对象的foo属性时,如果foo属性为null,计算结果将直接返回null,而不会引发NullPointerException异常。如以下代码所示:

1
2
3
4
5
// 使用安全操作,将输出null
System.out.println(
"----" + parser.parseExpression("#foo?.bar").getValue());
// 不使用安全操作,将引发NullPointerException
System.out.println(parser.parseExpression("#foo.bar").getValue());

13. 集合选择

SpEL允许直接对集合进行选择操作,这种选择操作可以根据指定表达式对集合元素进行筛选,只有符合条件的集合元素才会被选择出来。SpEL集合选择的语法格式如下:
collection.[condition_expression]
在上面的语法格式中, condition_expression是一个根据集合元素定义的表达式,只有当该表达式返回true时,对应的集合元素才会被筛选出来。如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<String> list = new ArrayList<String>();
list.add("疯狂Java讲义");
list.add("疯狂Ajax讲义");
list.add("疯狂iOS讲义");
list.add("经典Java EE企业应用实战");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("mylist", list);
// 判断集合元素length()方法的长度大于7,“疯狂iOS讲义”被剔除
Expression expr = parser.parseExpression("#mylist.?[length()>7]");
System.out.println(expr.getValue(ctx));
Map<String, Double> map = new HashMap<String, Double>();
map.put("Java", 89.0);
map.put("Spring", 82.0);
map.put("英语", 75.0);
ctx.setVariable("mymap", map);
// 判断Map集合的value值大于80,只保留前面2个Entry
expr = parser.parseExpression("#mymap.?[value>80]");
System.out.println(expr.getValue(ctx));

正如上面的粗体字代码所示,这种集合选择既可对List集合进行筛选,也可对Map集合进行筛选,当操作List集合时, condition_expression中访间的每个属性、方法都是以集合元素为主调的;当操作Map集合时,需要显式地用key引用Map Entrykey,用value引用Map Entryvalue.

1
2
[疯狂Java讲义, 疯狂Ajax讲义, 经典Java EE企业应用实战]
{Java=89.0, Spring=82.0}

14. 集合投影

SpEL允许对集合进行投影运算,这种投影运算将依次迭代每个集合元素,迭代时将根据指定表达式对集合元素进行计算得到一个新的结果,依次将每个结果收集成新的集合,这个新的集合将作为投影运算的结果。
SpEL投影运算的语法格式为:
collection.![condition_expression]
在上面的语法格式中, condition_expression是一个根据集合元素定义的表达式。上面的SpEL会把collection集合中的元素依次传入condition_expression中,每个元素得到一个新的结果,所有计算出来的结果所组成的新结果就是该表达式的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<String> list = new ArrayList<String>();
list.add("疯狂Java讲义");
list.add("疯狂Ajax讲义");
list.add("疯狂iOS讲义");
list.add("经典Java EE企业应用实战");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("mylist", list);
// 得到的新集合的元素是原集合的每个元素length()方法返回值
Expression expr = parser.parseExpression("#mylist.![length()]");
System.out.println(expr.getValue(ctx));
List<Person> list2 = new ArrayList<Person>();
list2.add(new Person(1, "孙悟空", 162));
list2.add(new Person(2, "猪八戒", 182));
list2.add(new Person(3, "牛魔王", 195));
ctx.setVariable("mylist2", list2);
// 得到的新集合的元素是原集合的每个元素name属性值
expr = parser.parseExpression("#mylist2.![name]");
System.out.println(expr.getValue(ctx));

运行结果:

1
2
[8, 8, 7, 15]
[孙悟空, 猪八戒, 牛魔王]

15. 表达式模板

表达式模板有点类似于带占位符的国际化消息。例如如下带占位符的国际化消息:

1
今天的股票价格是:{1}

上面的消息可能生成如下字符串:

1
今天的股票价格是:123

上面字符串中的123需要每次动态改变。
这种需求可以借助于SpEL的表达式模板的支持。表达式模板的本质是对”直接量表达式”的扩展它允许在”直接量表达式”中插入一个或多个并#{expression},#{expression}将会被动态计算出来。
例如,如下程序示范了使用表达式模板:

1
2
3
4
5
6
7
8
Person p1 = new Person(1, "孙悟空", 162);
Person p2 = new Person(2, "猪八戒", 182);
Expression expr = parser.parseExpression("我的名字是#{name},身高是#{height}",
new TemplateParserContext());
// 将使用p1对象name、height填充上面表达式模板中的#{}
System.out.println(expr.getValue(p1));
// 将使用p2对象name、height填充上面表达式模板中的#{}
System.out.println(expr.getValue(p2));

正如在上面的程序中所看到的,使用ExpressionParser解析字符串模板时需要传入一个TemplateParserContext参数,该TemplateParserContext实现了ParserContext接口,它用于为表达式解析传入一些额外的信息,例如TemplateParserContext指定解析时需要计算#{}之间的值。