Spring表达式语言(SpEL)
Spring也有自己的EL,叫Spring Expression Language,简称SpEl。其可以在程序中单独使用,也可以在Spring应用中进行bean定义时使用。其核心是org.springframework.expression.Expression接口,Spring使用该接口来表示EL中的表达式。通过Expression接口的系列getValue()方法我们可以获取对应Expression在特定EvaluationContext下的值,也可以通过其系列setValue()方法来设值。对应的Expression通常不是由我们直接来new对应实现类的实例,而是通过Spring提供的org.springframework.expression.ExpressionParser接口的系列parseExpression()方法来将一个字符串类型的表达式解析为一个Expression。以下是一个简单的示例,在该示例中我们将字符串表达式“1+2”解析为一个Expression,然后进行计算得出其值为3。
String expressionStr = "1+2"; ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(expressionStr); Integer val = expression.getValue(Integer.class); System.out.println(expressionStr + "的结果是:" + val);
最终输出:1+2的结果是:3
Expression接口有一系列的getValue()方法,当其不接收任何参数时表示将会把Expression的计算结果当做一个Object进行返回,如果我们希望返回的是特定的类型,则可以传递对应的类型作为getValue()方法的参数,如上述示例中传递的Interger.class。我们也可以通过给Expression的getValue()方法传递EvaluationContext用以获取在特定环境下的计算结果,也可以传递一个Object作为Expression计算的rootObject。
SpEL支持的表达式语法有:
- 文本表达式
- 对象属性表达式
- 数组、List 和 Map 表达式
- 方法表达式
- 操作符表达式
- 安全导航操作符
- 三元操作符
- Elvis 操作符
- 赋值表达式
- 类型操作符
- 创建对象操作符
- 变量表达式
- 集合选择表达式
- 集合元素布尔判断
一、文本表达式
文本表达式支持字符串、 数字(正数 、 实数及十六进制数) 、 布尔类型及 null。其中的字符表达式可使用单引号来表示,形如:'Deniro'。如果表达式中包含单引号或者双引号字符,那么可以使用转义字符 \。
数字支持负数 、小数、科学记数法、八进制数和十六进制数 。 默认情况下,实数使用 Double.parseDouble()
进行表达式类型转换 。
ExpressionParser parser = new SpelExpressionParser(); //字符串解析, 写成“Hello World”会报错。 String str = (String) parser.parseExpression("'Hello World'").getValue(); System.out.println(str); //整型解析 int intVal = (Integer) parser.parseExpression("0x2F").getValue(); System.out.println(intVal); //双精度浮点型解析 double doubleVal = (Double) parser.parseExpression("4329759E+22").getValue(); System.out.println(doubleVal); //布尔型解析 boolean booleanVal = (boolean) parser.parseExpression("true").getValue(); System.out.println(booleanVal);
二、对象属性表达式
在 SpEL 中,我们可以使用对象属性路径(形如类名.属性名.属性名
)来访问对象属性的值。
假设有一个账号类,Account.java:
public class Account { private String name; private int footballCount; private Friend friend; private List<Friend> friends; public Account(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setFootballCount(int footballCount) { this.footballCount = footballCount; } public int getFootballCount() { return footballCount; } public void setFriends(List<Friend> friends) { this.friends = friends; } public List<Friend> getFriends() { return friends; } public void setFriend(Friend friend) { this.friend = friend; } public Friend getFriend() { return friend; } }
它包含姓名 name、足球数 footballCount 和一个朋友 friend 属性。friend 属性是一个 Friend 类:
public class Friend { private String name; public Friend(String name) { this.name = name; } public String getName() { return name; } }
解析属性对象表达式
//初始化对象 Account account=new Account("Deniro"); account.setFootballCount(10); account.addFriend(new Friend("Jack")); //解析器 ExpressionParser parser = new SpelExpressionParser(); //解析上下文 EvaluationContext context=new StandardEvaluationContext(account); //获取不同类型的属性 String name= (String) parser.parseExpression("Name").getValue(context); System.out.println(name); int count= (Integer) parser.parseExpression("footballCount+1").getValue(context); System.out.println(count); //获取嵌套类中的属性 String friend= (String) parser.parseExpression("friend.name").getValue(context); System.out.println(friend);
总结:
- SpEL 解析器适应力强,属性名首字母大小写均可。
- 解析对象表达式时,需要传入 EvaluationContext 上下文参数。
三、数组、List 和 Map 表达式
数组表达式支持 Java 创建数组的语法,形如 new int[]{3,4,5},数组项之间以逗号作为分隔符。
注意:目前还不支持多维数组。Map 表达式以键值对的方式来定义,形如 {name:'deniro',footballCount:10}。
//解析器 ExpressionParser parser = new SpelExpressionParser(); //解析一维数组 int[] oneArray = (int[]) parser.parseExpression("new int[]{3,4,5}").getValue(); System.out.println("一维数组开始:"); for (int i : oneArray) { System.out.println(i); } System.out.println("一维数组结束"); //这里会抛出 SpelParseException //int[][] twoArray = (int[][]) parser.parseExpression("new int[][]{3,4,5}{3,4,5}") // .getValue(); //解析 list List list = (List) parser.parseExpression("{3,4,5}").getValue(); System.out.println("list:" + list); //解析 Map Map map = (Map) parser.parseExpression("{account:'deniro',footballCount:10}") .getValue(); System.out.println("map:" + map); //解析对象中的 list final Account account = new Account("Deniro"); Friend friend1 = new Friend("Jack"); Friend friend2 = new Friend("Rose"); List<Friend> friends = new ArrayList<>(); friends.add(friend1); friends.add(friend2); account.setFriends(friends); EvaluationContext context = new StandardEvaluationContext(account); String friendName = (String) parser.parseExpression("friends[0].name") .getValue(context); System.out.println("friendName:" + friendName);
从数组与 List 获取值,可以在括号内指定索引来获取,形如上例中的 friends[0]
。Map 中可通过键名来获取,形如 xxx['xxx']
。
四、方法表达式
String.contains('xxx')
。//解析器 ExpressionParser parser = new SpelExpressionParser(); //调用 String 方法 boolean isEmpty = parser.parseExpression("'Hi,everybody'.contains('Hi')").getValue(Boolean.class); System.out.println("isEmpty:" + isEmpty); /** * 调用对象相关方法 */ final Account account = new Account("Deniro"); EvaluationContext context = new StandardEvaluationContext(account); //调用公开方法 parser.parseExpression("setFootballCount(11)").getValue(context, Boolean.class); System.out.println("getFootballCount:" + account.getFootballCount()); //调用私有方法,抛出 SpelEvaluationException: EL1004E: Method call: Method write() cannot be found on net.deniro.spring4.spel.Account type //parser.parseExpression("write()").getValue(context,Boolean.class); //调用静态方法 parser.parseExpression("read()").getValue(context, Boolean.class); //调用待可变参数的方法 parser.parseExpression("addFriendNames('Jack','Rose')").getValue(context, Boolean.class);
注意:调用对象的私有方法会抛出异常。
五、操作符表达式
关系操作符
SpEL 支持 Java 标准操作符:等于、不等于、小于、小等于、大于、大等于、正则表达式和 instanceof 操作符。
//解析器 ExpressionParser parser = new SpelExpressionParser(); //数值比较 boolean result = parser.parseExpression("2>1").getValue(Boolean.class); System.out.println("2>1:" + result); //字符串比较 result = parser.parseExpression("'z'>'a'").getValue(Boolean.class); System.out.println("'z'>'a':" + result); //instanceof 运算符 result = parser.parseExpression("'str' instanceof T(String)").getValue(Boolean.class); System.out.println("'str' 是否为字符串 :" + result); result = parser.parseExpression("1 instanceof T(Integer)").getValue(Boolean.class); System.out.println("1 是否为整型 :" + result); //正则表达式 result = parser.parseExpression("22 matches '\\d{2}'").getValue(Boolean.class); System.out.println("22 是否为两位数字 :" + result);
instanceof 操作符后面是类型表达式,格式为 T(Java 包装器类型)
,如整型 T(Integer)
。注意:不能使用原生类型,如果这样 T(int)
会返回错误的判断结果。
matches 用于定义正则表达式,之后跟着单引号包裹着的正则表达式。
逻辑操作符
逻辑操作符支持以下操作:
逻辑操作符 | 说明 |
---|---|
and 或 && |
与操作 |
or 或 || |
或操作 |
! |
非操作 |
注意: 在 SpEL 中,不仅支持 Java 标准的逻辑操作符,还支持 and 与 or 关键字。
//解析器 ExpressionParser parser = new SpelExpressionParser(); //与操作 boolean result = parser.parseExpression("true && true").getValue(Boolean.class); System.out.println("与操作:" + result); //或操作 result = parser.parseExpression("true || false").getValue(Boolean.class); System.out.println("或操作:" + result); parser.parseExpression("true or false").getValue(Boolean.class); System.out.println("或操作(or 关键字):" + result); //非操作 result = parser.parseExpression("!false").getValue(Boolean.class); System.out.println("非操作:" + result); //抛出 SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.lang.Integer to java.lang.Boolean //parser.parseExpression("!0").getValue(Boolean.class);
注意:逻辑操作符前后运算结果必须是布尔类型,否则会抛出 SpelEvaluationException。
运算操作符
SpEL 支持 Java 运算操作符,并遵守运算符优先级规则:
运算操作符 | 说明 | 支持的操作数类型 |
---|---|---|
+ | 加法 | 数字、字符串或日期 |
- | 减法 | 数字或日期 |
* | 乘法 | 数字 |
/ |
除法 | 数字 |
% | 取模 | 数字 |
^ | 指数幂 | 数字 |
//解析器 ExpressionParser parser = new SpelExpressionParser(); //加法运算 Integer iResult = parser.parseExpression("2+3").getValue(Integer.class); System.out.println("加法运算:" + iResult); String sResult = parser.parseExpression("'Hi,'+'everybody'").getValue(String.class); System.out.println("字符串拼接运算:" + sResult); //减法运算 iResult = parser.parseExpression("2-3").getValue(Integer.class); System.out.println("减法运算:" + iResult); //乘法运算 iResult = parser.parseExpression("2*3").getValue(Integer.class); System.out.println("乘法运算:" + iResult); //除法运算 iResult = parser.parseExpression("4/2").getValue(Integer.class); System.out.println("除法运算:" + iResult); Double dResult = parser.parseExpression("4/2.5").getValue(Double.class); System.out.println("除法运算:" + dResult); //求余运算 iResult = parser.parseExpression("5%2").getValue(Integer.class); System.out.println("求余运算:" + iResult);
六、安全导航操作符
.
” 之前加一个 “?
”。final Account account = new Account("Deniro"); account.setFriend(new Friend("Jack")); //解析器 ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(account); String friendName = parser.parseExpression("friend?.name").getValue(context, String.class); System.out.println("friendName:" + friendName); //设置为 null account.setFriend(null); friendName = parser.parseExpression("friend?.name").getValue(context, String.class); //打印出 null System.out.println("friendName:" + friendName);
这里会先判断 friend 对象是否为空;如果为空,则返回 "null" 字符串;否则返回需要的属性值。
七、三元运算符
SpEL 支持标准的 Java 三元操作符:<表达式 1>?<表达式 2>:<表达式 3>
ExpressionParser parser = new SpelExpressionParser(); boolean result = parser.parseExpression("(1+2) == 3?true:false").getValue(Boolean.class); System.out.println("result:" + result);
八、Elvis 操作符
Elvis 操作符是在 Groovy 中使用的三元操作符简化版。
在三元操作符中,我们一般需要写两次变量名,比如下面代码段中的 title:
String title="News"; String actualTitle=(title!=null)?title:"tip";
使用 Elvis 操作符后,可以将上述代码段简写为:
title?:"tip"
SpEL 支持的 Elvis 操作符格式是:<var>?:<value>
,如果 var 变量为 null,那就取 value 值,否则就取自身的值。所以 Elvis 操作符很适合用于设置默认值。
final Account account = new Account("Deniro"); //解析器 ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(account); String friendName = parser.parseExpression("name?:'无名'").getValue(context, String.class); System.out.println("friendName:" + friendName); //设置名字为 null account.setName(null); friendName = parser.parseExpression("name?:'无名'").getValue(context, String.class); System.out.println("friendName:" + friendName);
九、赋值表达式
可以通过赋值表达式来设置属性的值,效果等同于调用 setValue() 方法。
final Account account = new Account("Deniro"); //解析器 ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(account); String name=parser.parseExpression("name='Jack'").getValue(context,String.class); System.out.println("name:"+name);
十、类型操作符
类型操作符 T 可以从类路径加载指定类名称(全限定名)所对应的 Class 的实例,格式为:T(全限定类名)
,效果等同于 ClassLoader#loadClass()
。
ExpressionParser parser = new SpelExpressionParser(); //加载 java.lang.Integer Class integerClass = parser.parseExpression("T(Integer)").getValue(Class.class); System.out.println(integerClass == java.lang.Integer.class); //加载 net.deniro.spring4.spel.Account Class accountClass = parser.parseExpression("T(com.codedot.spel.Account)").getValue(Class.class); System.out.println(accountClass == Account.class); //调用类静态方法 double result = (double) parser.parseExpression("T(Math).abs(-2.5)").getValue(); System.out.println("result:" + result);
我们还可以直接通过 T 操作符调用类的静态方法,格式为 T(全限定类名).静态方法名
,比如上面例子中求某数的绝对值 T(Math).abs(-2.5)
。
SpEL 中会使用 StandardTypeLocator#findType() 方法来加载类。 findType 方法定义如下:
public Class<?> findType(String typeName) throws EvaluationException { String nameToLookup = typeName; try { return ClassUtils.forName(nameToLookup, this.classLoader); } catch (ClassNotFoundException ey) { // try any registered prefixes before giving up } for (String prefix : this.knownPackagePrefixes) { try { nameToLookup = prefix + '.' + typeName; return ClassUtils.forName(nameToLookup, this.classLoader); } catch (ClassNotFoundException ex) { // might be a different prefix } } throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); }
- 尝试直接加载类。
- 如果找不到,则尝试从已注册的包前缀(java.lang)下加载类。所以如果需要加载的类在 java.lang 下,那么可以直接写类名。
- 如果都找不到,则抛出 SpelEvaluationException 异常。
十一、创建对象操作符
可以使用 new 操作符来创建一个新对象 。 除了基本类型(如整型、布尔型等)和字符串之外,创建其它类需要指明全限定类名( 包括包路径 ) 。
ExpressionParser parser = new SpelExpressionParser(); Account account = parser.parseExpression("new com.codedot.spel.Account" + "('Deniro')").getValue(Account.class); System.out.println("name:"+account.getName());
十二、变量表达式
可以通过 #变量名
来引用在 EvaluationContext 中定义的变量。通过 EvaluationContext#setVariable(name, val)
即可定义新的变量;name 表示变量名,val 表示变量值。
如果变量是集合,比如 list,那么可以通过 #scores.[#this]
来引用集合中的元素。
Account account = new Account("Deniro"); ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(account); //定义一个新变量,名为 newVal context.setVariable("newVal", "Jack"); //获取变量 newVal 的值,并赋值给 User 的 name 属性 parser.parseExpression("name=#newVal").getValue(context); System.out.println("getName:" + account.getName()); //this 操作符表示集合中的某个元素 List<Double> scores = new ArrayList<>(); scores.addAll(Arrays.asList(23.1, 82.3, 55.9)); context.setVariable("scores", scores);//在上下文中定义 scores 变量 List<Double> scoresGreat80 = (List<Double>) parser.parseExpression("#scores.?[#this>80]") .getValue(context); System.out.println("scoresGreat80:" + scoresGreat80);
十三、集合选择表达式
?[selectionExpression]
。选择符合条件的结果集的第一个元素的语法为 ^ [selectionExpression]
,选择最后一个元素的语法为 $[selectionExpression]
。选择表达式也可应用于 Map 。List<Integer> list = new ArrayList(); list.add(10); list.add(21); list.add(8); list.add(33); ExpressionParser parser = new SpelExpressionParser(); //过滤 list 集合中的元素 StandardEvaluationContext listContext = new StandardEvaluationContext(list); List<Integer> great4List = (List<Integer>) parser.parseExpression("?[#this>4]").getValue(listContext); System.out.println("great4List:" + great4List); //获取匹配元素中的第一个值 Integer first = (Integer) parser.parseExpression("^[#this>2]").getValue(listContext); System.out.println("first:" + first); //获取匹配元素中的最后一个值 Integer end = (Integer) parser.parseExpression("$[#this>2]").getValue(listContext); System.out.println("end:" + end);
对于 List 和 Set ,是针对集合中的每一个元素进行比较的;而对于 Map,则可以指定是元素的键(key)还是元素的值进行比较的。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); //过滤 Map Map<String, Double> rank = new HashMap<>(); rank.put("Deniro", 96.5); rank.put("Jack", 85.3); rank.put("Lily", 91.1); context.setVariable("Rank", rank); //value 大于 90 Map<String, Double> rankGreat95 = (Map<String, Double>) parser.parseExpression ("#Rank.?[value>90]").getValue(context); System.out.println("rankGreat95:" + rankGreat95); //key 按字母顺序,排在 L 后面 Map<String, Double> afterL = (Map<String, Double>) parser.parseExpression("#Rank.?[key>'L']").getValue(context); System.out.println("afterL:" + afterL);
十四、集合元素布尔判断
通过表达式 ![projectionExpression]
,我们可以判断集合中每一个元素是否符合表达式规则。
ExpressionParser parser = new SpelExpressionParser(); List list = (List) parser.parseExpression("{3,4,5}").getValue(); System.out.println("list:" + list); List<Boolean> isgreat4=(List<Boolean>)parser.parseExpression("![#this>3]").getValue(list); System.out.println("isgreat4:" + isgreat4);