一、Evaluation
Spring中支持SpEL功能的类和接口都在包 org.springframework.expression
内,下面是一些简单的SpEL使用示例:
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello world'.concat('!')"); String message = (String) exp.getValue(); System.out.println(message); // invokes 'getBytes()' Expression exp = parser.parseExpression("'Hello World'.bytes"); byte[] bytes = (byte[]) exp.getValue(); // invokes 'getBytes().length' Expression exp = parser.parseExpression("'Hello World'.bytes.length"); Integer length = exp.getValue(Integer.class);
ExpressionParser
接口用来解析EL表达式字符串,从上面的例子可以看出,想要使用EL表达式功能,只需创建一个解析器,然后从解析后的结果中获取对应类型的值。获取解析结果可以是通过强制类型转换,也可以通过泛型获取。
1. EvaluationContext
EvaluationContext
接口用于解析属性、方法或字段,还有类型转换,Spring提供了两个实现:
SimpleEvaluationContext
:提供了SpEL语言的特性和配置项的子集,不需要完整的SpEL功能应该使用该实现。它不包括数据绑定表达式和基于属性的过滤器。StandardEvaluationContext
:提供了全部的SpEL语言的特性和配置项,你可以用它定义一个默认的根对象并且配置每个有效的解析相关的策略。
2. 配置
我们可以使用 org.springframework.expression.spel.SpelParserConfiguration
对象来配置 SpEL 表达式,用来控制表达式组件的一些行为,例如:当创建的数组或集合默认为 null
元素,可以自动创建元素。当创建的数组或列表超过了最大长度,可以自动增加数组的或列表的长度。如下代码所示:
SpelParserConfiguration config = new SpelParserConfiguration(true, true); SpelExpressionParser parser = new SpelExpressionParser(config); Expression expression = parser.parseExpression("list[3]"); Demo demo = new Demo(); expression.getValue(demo); System.out.println(demo.list);
二、使用
不管是基于 XML 还是基于注解配置 Bean 时,你都可以使用 SpEL 表达式,这两种情况下都是用 #{}
语法来实现。
1. 基于XML
<bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <!-- other properties --> </bean>
2. 基于注解
你可以在字段、方法、方法参数或构造器参数上使用 @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; } }
三、语法
1. 文本
文本表达式类型支持字符串、数值(整型、浮点型、十六进制等)、布尔型、和 null
,字符串使用单引号定义,例如:
ExpressionParser parser = new SpelExpressionParser(); String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); Object nullValue = parser.parseExpression("null").getValue();
数值支持负数、指数和小数点,默认情况下,浮点数使用 Double.parseDouble()
来解析。
2. 属性、数组 、集合
获取对象的属性很简单,只需要使用 .
号来获取,支持嵌套获取,并且属性的首字母大小写不敏感。
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(); String invention = parser.parseExpression("inventions[3]").getValue(context, tesla, String.class); String name = parser.parseExpression("Members[0].Name").getValue(context, ieee, String.class); String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(context, ieee, String.class);
使用方括号来获取 Map 的数据元素,方括号内使用单括号来表示 Key。
Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class); String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, String.class); parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
3. 行内List
你可以直接使用 {}
来表示列表,它本身表示一个空的列表。
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
4. 行内Maps
你可以直接使用 {key:value}
来表示 Map,{:}
表示一个空的 Map,表示 Map 的表达式中,Key 的单引号可以省略。
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);
5. 数组构造器
你可以使用和 Java 一样的语法来构建一个数组,数组的初始化数据是可选的,但暂时不支持多维数组的初始化。
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
6. 方法
调用方法使用典型的 Java 语法,你可以在文本表达式中调用任何方法,也支持变量参数。
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class);
7. 运算符
在 SpEL 表达式中,支持下面讲到的四类运算符:关系运算符、逻辑运算符、数学运算符和赋值运算符。
关系运算符
支持各种关系运算符(=,!=,<,<=,>,>=
),如下所示:
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
对关于 null
的大小比较遵循一个简单的规则:null
被看成 nothing(不是0),这样的话就会有,所有的值都大于 null
(X > null
总是为 true
),没有值会小于 null
(X < null
总是为 false
)。
如果你是数值比较,避免与
null
进行比较,而使用 0 来代替。
除此之外,SpEL 来支持 instanceof
运算符和基于正则表达式的 matches
运算符。
boolean falseValue = parser.parseExpression("'xyz' instanceof T(Integer)").getValue(Boolean.class); boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); boolean falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
注意:原始数据类型会自动装箱,所以 1 instanceof T(int)
会返回 false
而 1 instanceof T(Integer)
会返回 true
。
每个运算符都可以使用英文单词来代替,为了避免运算符在特殊文件下有其他的含义,比如在 XML 文件中,如下:
lt
(<
)gt
(>
)le
(<=
)ge
(>=
)eq
(==
)ne
(!=
)div
(/
)mod
(%
)not
(!
)
所有上面的文字运算符都大小写不敏感。
逻辑运算符
支持各种逻辑运算符(and,or,not
),如下所示:
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
数学运算符
支持各种数学运算符(+,-,*,/,%,^
),^
是作指数运算,如下所示:
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1. int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
赋值运算符
设置一个属性,可以使用赋值符号(=
)来进行赋值,它相当于调用 setValue
方法。
Inventor inventor = new Inventor(); EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic"); String aleks = parser.parseExpression("Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
8. 类型
你可以使用 T
来指定一个 java.lang.Class
的类型实例,静态方法可以直接使用它来调用,在 StandardEvaluationContext
中使用 TypeLocator
来寻找类型,StandardTypeLocator
(可以被替换)支持解析 java.lang
包,所以使用 T()
来获取 java.lang
包下的类可以不用全路径。
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);
9. 构造器
你可以使用 new
来调用构造器方法,你需要指定创建类的全路径除了基于类型(int,float
等)和 String
之外。
Inventor einstein = p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')").getValue(Inventor.class); p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))").getValue(societyContext);
10. 变量
你可以使用 #varName
语法来获取变量。
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
引用当前上文的 root 对象。
List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess(); context.setVariable("primes", primes); List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
11. 函数
你可以使用 SpEL 来自定义函数,来简化方法调用。
Method method = ...; EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method);
例如定义一个方法来实现倒置字符串。
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(); } }
在 SpEL 中注册前面的方法并使用。
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);
12. Bean引用
如果在上下文中配置了一个自定义 BeanResolver
,你可以通过 @
符号来获取 Bean。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); Object bean = parser.parseExpression("@something").getValue(context);
如果需要访问 factory bean,你可以使用 &
符号来获取。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); Object bean = parser.parseExpression("&foo").getValue(context);
13. 三元运算符(If-Then-Else)
你可以很简单的使用三目运算符。
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class); parser.parseExpression("Name").setValue(societyContext, "IEEE"); societyContext.setVariable("queryName", "Nikola Tesla"); expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; String queryResultString = parser.parseExpression(expression).getValue(societyContext, String.class);
14. Elvis 运算符
Elvis 运算符是三元运算符的简单写法,例如:
String name = "Elvis Presley"; String displayName = (name != null ? name : "Unknown");
使用 Elvis 运算符写法如下:
ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(String.class); System.out.println(name); // 'Unknown' EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Nikola Tesla tesla.setName(null); name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Elvis Presley
15. 安全运算符
安全运算符可以有效避免 NullPointerException
,当你访问一个空对象的属性或方法时,安全运算符会返回 null
而不抛异常。
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!!!
16. 集合读取
SpEL 对集合的读取提供了强大的语法特性,你可以使用 .?[selectExpression]
语法来设置一些集合查询元素的过滤条件。
List<Inventor> list = (List<Inventor>) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
集合的读取对列表和 Map 都有用,对于列表它会遍历每个数据元素,对 Map 它会遍历 Map.Entry
,Key 和 Value 都可以设置查询条件。
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回集合所有元素外,你可以使用 .^[selectExpression]
来返回第一个元素,和使用 .$[selectExpression]
来返回最后一个元素。
17. 集合映射
我们可以通过表达式从一个集合映射一个属性的集合,比如已知一个人员列表,需要获取人员出生地的列表。
// returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
18. 表达式模板
表达式模板可以混合普通文本字符串和表达式字符串,每个表达式块使用自定义的前缀和后缀,例如:
String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // evaluates to "random number is 0.7038186818312008"
前面的表达式是通过 #{ }
来确实的,那是因为我们定义了下面的模板上下文类:
public class TemplateParserContext implements ParserContext { public String getExpressionPrefix() { return "#{"; } public String getExpressionSuffix() { return "}"; } public boolean isTemplate() { return true; } }
SpEL 表达式拥有强大的语法特性,在项目中适当使用可以减少代码量,优化代码结构,也可以说是 Spring 中一个很强大的部分。