10 Spring Expression Language (SpEL)
10.1 简介
Spring Expression Language是一个强大的表达式语言,支持在运行时查询和操作对象。这个语言的语法跟Unified EL类似,但是他提供额外的功能,特别是方法调用和基本的字符串模板功能。
虽然Java也有几种Java表达式语言,像OGNL,MVEL和JBoss EL,而Spring的表达式语言为SPring提供了一个很好的支持表达式语言,可以用在Spring下的所有产品中。其语言特性是受Spring产品中的项目要求所驱动的,包括基于Spring Tool Suite的eclipse中代码完成支持的工具要求。也就是说,SpEL是基于不可知论API的技术,在需要的时候允许整合其他的表达式语言实现。
虽然Spring是作为Spring组合中表达式评估的基础,但是与Spring并没有直接的联系,所以他是可以低浓度使用的。本章的很多例子都是将SpEL当做一个独立的表达式语言看待,所以它需要一些引导的基础类,比如解析器。大多数Spring用户不需要了解这些基础设施,主需要评估一下表达式字符串。这个典型用途是整合SpEL到创建基于bean定义的XML或注解中(在 Expression support for defining bean definitions中有体现)。
本章涵盖表达式语言的特征,API以及语言语法。在一些地方,Inventor和Inventor的Society类用于表达式评估的目标对象。这些类的声明将在本章末尾列出。
10.2 功能概述
表达式语言支持以下功能:
- 文字表达
- 布尔和关系运算符
- 正则表达式
- 类表达式
- 访问属性,数组,列表,map
- 方法调用
- 关系运算符
- 分配(Assignment)
- 调用构造函数
- Bean引用
- 数组构造
- 内联列表
- 内联map
- Ternary operator
- 变量
- 用户自定义功能
- Collection projection
- Collection selection
- 模板表达式
10.3 Spring的表达式接口评估表达式
本小节介绍SpEL接口和表达式语言的简单实用,完整的语言引用可以参考”“语言引用(Language Reference)”部分。
下面这些代码介绍使用SpELl API评估文字表达式”“Hello World”:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = paser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
message变量最后的值为‘Hello World’。
经常使用的SpEL类和接口位于包org.springframework.expression
中和它的子包spel.support
。
接口ExpressionParser
用于解析表达式字符串的,在这个例子中,表达式字符串是被单括号括起来的文字表达式。而接口Expression则是用来评估之前定义的表达式字符串。当调用parser.parseExpression
和exp.getValue
这两个方法时,需要分别抛出ParseException和EvaluationException
异常。
SpEL支持很多功能,比如调用方法,访问属性和调用构造器。
一个方法调用的例子,我们调用文字字符串的concat
方法:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
message的值是“Hello World!”。
一个调用JavaBean属性的例子,调用String的属性Bytes:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
SpEL也支持嵌套属性,用标准的.符号,像prop1.prop2.prop3和属性值的设置,也可以访问公共字段。
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
还可以调用String的构造器:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
注意使用泛型方法public <T> T getValue(Class<T> desiredResultType)
。使用这个方法不需要将表达式的值转成结果类型。如果值不能转成类型T
或注册类型转换器转换不成功,会抛出EvaluationException
异常。
SpEL更常用的方法是提供一个针对特定对象实例(根对象)进行评估的表达式字符串。有两个选项,选择哪一个取决于。在下面的例子中我们从Inventor类实例中获取name属性。
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
例子中的最后一行的name变量的值为“Nikola Tesla”。StandardEvaluationContext
类就是name
属性被评估的地方,如果根对象不太可能会改变,在评估文本中只会被设置一次。如果根对象频繁的更改,就可以调用getValue
,像下面例子一样:
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = (String) exp.getValue(tesla);
在这个例子中tesla
直接用到getValue
中,表达式评估设备创建和管理一个默认的评估上下文-它不需要提供。
相对来说,创建StandardEvaluationContext的成本略高,因为在重复利用中建立缓存状态,能够更快的执行下后续的表达式评估。基于这个原因,要尽可能的缓存和重利用,而不是为每一个表达式创建一个新的评估。
在某些情况下可能需要使用配置的评估上下文,但在调用getValue时仍然需要提供一个不同的根对象。getValue在同一个调用中,可以允许两个特例。在某些情况下,调用的根对象被认为是覆盖了评估上下文的任何(可能为空)特例。
作为最后一个例子,用上面的例子讲一个显示布尔类型的用法:
Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(context, Boolean.class); // evaluates to true
10.3.1 EvaluationContext接口
EvaluationContext
接口用在评估需要解决属性,方法,字段的表达式,来执行类型转换的需求。StandardEvaluationContext
使用反射来操作对象,缓存java.lang.reflect.Method
,java.lang.reflect.Field
和java.lang.reflect.Constructor
实例以提高性能。
StandardEvaluationContext
是根据方法setRootObject()
或把根对象放入构造器中来评估根对象。同样也可以用方法setVariable()
和registerFunction()
用在表达式中特殊的变量和功能。StandardEvaluationContext
也是可以注册自定义ConstructorResolver
,MethodResolver
和PropertyAccessor
来扩展SpEL评估表达式的地方。
类型转换
SpEL默认使用Spring core(org.springframework.core.convert.ConversionService)的转换服务。这个转换服务有很多内置的常用的转换器,但也可以扩展添加类型之间自定义转换器。此外,它具有泛型感知的强大功能,这意味着,当在表达式中含有泛型时,SpEL将会尝试转换以维持任何对象类型正确性。
在实际开发中怎么用呢?假设使用setValue()被设置一个List属性,这个属性实际上是List。SpEL将会在被放置之前把list元素转成Boolean。一个简单例子:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
// b will be false
Boolean b = simple.booleanList.get(0);
10.3.2 解析配置
用解析配置对象(org.springframework.expression.spel.SpelParserConfiguration
)可以配置SpEL表达式解析,配置对象控制表达式组件的行为。例如,如果数组或集合的某个索引元素为null,配置对象就可以自动创建一个元素。这对使用一组属性引用的表达式很有用处。当索引数值超过数组或列表本身长度时,会自动的增加他们的长度。
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
当然还可以配置SpEL编译器的行为。
10.3.3 SPEL编译
Spring Framework 4.1包含一个基本的表达式编译器。表达式通常在评估阶段提供很多动态灵活功能,而不是最优的性能。对于偶然的几次使用表达式这是没问题的,但是如果是其他组件(比如Spring Integration)使用的话,需要的是性能而不是动态灵活。
而新的SpEL编译器打算处理这个需求。编译器将在评估过程中生成一个Java类以体现表达式的行为,这样可以获得更快的表达式评估。由于缺少表达式类,编译器在执行编译时会在评估阶段就行信息收集。例如,刚开始时,编译器不知道引用属性的类型,但是通过第一次的解释性评估,它就知道类型是什么。当然,如果不同的表达式元素随时间而变化的话,编译器获得的信息接下来也会有问题发生。所以编译器适用于类型信息不会发生改变的表达式。
一个基本的表达式像这样:
someArray[0].someProperty.someOtherProperty < 0.1
表达式设计数组访问,属性的引用和数值运算,能很明显的看到性能的提升。5000此迭代的例子中,之前的评估需要75ms,而使用编译器评估只需要3ms。
编译器配置
编译器不是默认开启的,有两种方式开启。一种是在之前讲的解析配置中打开,另一种是通过SpEL嵌入到其他组件的系统属性打开。本小节会讨论这两种操作。
编译器运行的模式都在枚举org.springframework.expression.spel.SpelCompilerMode
中。模式如下:
- OFF - 默认关闭。
- IMMEDIATE - 立即模式,表达式会被尽快执行。这个模式通常是在第一次解释性评估之后。如果执行失败,会收到异常报告。
- MIXED - 混合模式中,表达式会在解释模式和编译模式之间切换。经过一些解释运行后会转成编译模式;然后如果编译格式报错(像之前讲的类型变换),就自动转成解释模式,然后循环往复。基本上
IMMEDIATE
模式的异常都是内部自己解决了。
IMMEDIATE
模式存在的目的是MIXED模式会导致有副作用的表达式问题。如果一个编译器表达式在部分成功后爆炸,可能会影响系统的状态。在这种情况下,调用应该不会重新运行这个模式,因为一些表达式需要运行两次。
选择运行模式之后,用SpelParserConfiguration
配置解析器:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
当编译器模式确定时,也可以指定类加载器(允许传null值),编译器表达式被定义在任何提供的子类加载器中。重要的是确保子类加载器是否确定,它能看到表达式评估过程中的所有类型。如果子类加载器没有确定的话,会直接使用默认的(通常是表达式评估期间运行的线程的上下文类加载器)。
第二种方法也即是SpEL嵌入到其他组件中时,不太可能通过配置对象进行配置。这种情况下就会使用系统属性。spring.expression.compiler.mode
属性可以设置为SpelCompilerMode
枚举值(off
, immediate
, 或mixed
)的其中一个。
编译器限制
虽然已经有了Spring Framework 4.1的基本编译器框架,但是它并不支持表达式的每个类型。最初的关注点是在关键环境中常用的表达式。以下是不支持的表达式:
- 涉及到分配的表达式;
- 依赖转换器服务的表达式;
- 使用自定义解析器或表达器的表达式;
- 使用selection or projection的表达式。
10.4 Expression support for defining bean definitions
SpEL表达式可以与用于定义BeanDefinitionXML
配置元素的XML或注解两种方法一起使用。在这两种情况下,定义表达式的格式为#{<expression string>}
。
10.4.1 基于XML配置
属性值或构造器参数可以用表达式表示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
systemProperties
变量被重新定义,所以你可以在自己的表达式中使用。不需要在重新定义的变量前加#
。
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
还可以通过属性的名称引用:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
10.4.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;
}
}
以下是展示在setter方法上注解:
public static class PropertyValueTestBean
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
注解用到参数中的例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
10.5 Language Reference
10.5.1 文字表达式
文字表达式支持的类型有字符串,数值(int,real,hex),布尔和null。字符串类型用单引号分割。还要这个但因好的字符型放到双引号中。
下面例子是文字表达式的简单使用。通常,实际使用中他不会这么简单,而是作为更复杂的表达式,比如说在正则表达式的一侧。
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
10.5.2 Properties, Arrays, Lists, Maps, Indexers
属性引用是很简单的:只需要用‘.’表达嵌套的属性值:
// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
数组和列表的例子:
ExpressionParser parser = new SpelExpressionParser();
// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
teslaContext, String.class);
// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
societyContext, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
societyContext, String.class);
Map的例子:
// Officer's Dictionary
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
10.5.3 Inline lists
Lists可以用{}
表达:
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
{}
本身表达一个空的list。处于对性能考虑,如果列表都是由文字组成,系统会创建一个常量列表而不是每个评估都会建一个新的列表。
10.5.4 Inine Maps
Maps可以直接用{key:value}表示:
// evaluates to a Java map containing the two entries
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);
{:}表示一个空的map。为性能考虑同上。
10.5.5 Array construction
可以用Java数组构建数组,在构造时就可以 初始化填充这个数组:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
当构造多维数组时目前不充许提供初始化。
10.5.6 Methods
方法包括经典的Java语法。
// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
10.5.7 Operators
Relational operators
关系运算符包括“==”“!=”“<””<=” “>” “>=”.
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
除了支持关系运算,SpEL还支持instanceof
和matches
运算:
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
每个字符操作可以单纯的用字母代替。因为这可以米面字符在文档中有特殊含义。以下是等价的符号: lt
(<
), gt
(>
), le
(⇐
), ge
(>=
), eq
(==
), ne
(!=
), div
(/
), mod
(%
), not
(!
)。不区分大小写。
Logical operators
逻辑运算符有and,or和not。
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Mathematical operators
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
10.5.8 Assignment
Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
10.5.9 Types
T
操作符代表java.lang.Class(the type)的一个实例。
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);
10.5.10 Constructors
类名不能与基本类型和String一样。
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
//create new inventor instance within add method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
10.5.11 Variables
表达式中的变量的表达式语法#variableName
。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context);
System.out.println(tesla.getName()) // "Mike Tesla"
The #this and #root variables
#this
变量指的是当前的评估对象,#object
变量指的是根上下文对象。
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
10.5.12 Functions
可以通过注册可以在表达式字符串中调用的用户定义的函数来扩展SpEL。该功能使用该方法使用StandardEvaluationContext
进行注册。
public void registerFunction(String name, Method m)
Java方法的引用提供方法的实现。例如,以下是反转字符串的实用方法:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
然后将这个方法注册到评估上下文中,就可以在表达式中使用它:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
10.5.13 Bean references
如果使用bean解析器配置了评估上下文,则可以通过(@)符号在表达式中查找beans:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);
要访问工厂bean本身,需要在bean名称前加(&)符号:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
10.5.14 Ternary Operator (If-Then-Else)
可以使用三元运算符,在表达式中执行if-then-else条件逻辑。一个小例子:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
在这个例子中,会返回‘falseExp’字符串值。一个更实际的例子:
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);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
10.5.15 Elvis Operator
Elvis操作符缩短了三元操作符,在Groovy语言中使用。在三元操作符中,需要重复一个变量两次,例如:
String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";
相反,你可以使用Elvis操作符,命名与Elvis相似:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
还有一个更复杂的例子:
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Elvis Presley
10.5.16 Safe Navigation operator
安全导航运算符用于避免抛出NullPointerException
异常,它来自于Groovy语言。通常在引用一个对象时,需要在访问对象的方法或属性之前验证它是否为空。为了避免这种情况,安全导航操作会简单的返回一个null,而不是抛出异常。
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
10.5.17 Collection Selection
选择是表达式语言中的一个强大的功能,它允许你通过从一些条目里选择将一些源集合转化为另外一个。
选择用的语法是.?[selectionExpression]
。它会过滤掉这个集合,然后返回一个包含原始元素子集的新集合。例如,选择将很快得到Serbian发明者的列表:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
列表和map都可以进行选择。在前一种情况下,根据单独列表元素进行评估标准,同时针对map,根据每个map的entry进行评估标准(Java类型Map.Entry
)。Map输入中有key和value,可以作为选择中访问的属性。
下面这个表达式将会返回一个由原始元素集合组成的新集合,其中value都小于27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
为了返回所有选定元素,还可以检索第一个或最后一个值。匹配第一个元素的语法^[...]
,匹配最后一个元素的语法是$[...]
。
10.5.18 Collection Projection
Projection(翻译不出来)允许一个集合驱动子表达式的评估,返回一个新的集合,它的语法是![projectionExpression]
。更容易理解的例子,假设有一个inventors的列表,我们想找到他们出生城市的列表。我们将会评估每个inventor输入‘placeOfBirth.city’。使用projection:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
当然,map也可以使用,同上。
10.5.19 Expression templating
表达式模板允许文本域一个或多个评估块混合一起。每个评估模块都可以用自定义的前缀和后缀进行分隔,常用#{}
作为分隔符。例如:
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;
}
}
10.6 例子中用到的类
Inventor.java
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality) {
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
PlaceOfBirth.java
package org.spring.samples.spel.inventor;
public class PlaceOfBirth {
private String city;
private String country;
public PlaceOfBirth(String city) {
this.city=city;
}
public PlaceOfBirth(String city, String country) {
this(city);
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String s) {
this.city = s;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}