4.Spring表达式语言(SpEL)
Spring表达式语言(简称 SpEL
)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。语言语法与Unified EL相似,但提供了其他功能,最著名的是方法调用和基本的字符串模板功能。
Spring Expression Language的创建是为了向Spring社区提供一种受良好支持的表达式语言,该语言可用于该版本中的所有产品。它并不直接与Spring绑定,可以独立使用。
4.1。评价(Evaluation)
以下代码介绍了SpEL API来评估文字字符串表达式 Hello World。
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
您最可能使用的SpEL类和接口位于 org.springframework.expression
包及其子包中,例如spel.support
。
ExpressionParser
接口负责解析表达式字符串。 在前面的示例中,表达式字符串是由周围的单引号表示的字符串文字。 Expression
接口负责评估先前定义的表达式字符串。 分别调用parser.parseExpression
和exp.getValue
时,可以引发两个异常,ParseException
和EvaluationException
。
SpEL支持多种功能,例如调用方法,访问属性和调用构造函数。
SpEL还通过使用标准的点符号(例如prop1.prop2.prop3
)以及相应的属性值设置来支持嵌套属性 。也可以访问公共字段。
// 调用方法
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
// 调用属性
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
// 使用点表示法获取文字的长度
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
// 调用String的构造函数,而不是使用字符串文字
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"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
4.1.1。理解EvaluationContext
在评估表达式以解析属性,方法或字段并帮助执行类型转换时,使用EvaluationContext
接口。Spring提供了两种实现。
SimpleEvaluationContext
:针对不需要全部SpEL语言语法且应受到有意义限制的表达式类别,公开了SpEL基本语言功能和配置选项的子集。示例包括但不限于数据绑定表达式和基于属性的过滤器。StandardEvaluationContext
:公开全部SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
SimpleEvaluationContext
设计为仅支持SpEL语言语法的子集。它不包括Java类型引用,构造函数和Bean引用。它还要求您明确选择对表达式中的属性和方法的支持级别。默认情况下,create()
静态工厂方法仅启用对属性的读取访问。您还可以获取构建器来配置所需的确切支持级别,并针对以下一种或多种组合:
- 仅自定义
PropertyAccessor
(无反射) - 只读访问的数据绑定属性
- 读写的数据绑定属性
类型转换
默认情况下,SpEL使用Spring核心中可用的转换服务(org.springframework.core.convert.ConversionService
)。 此转换服务附带许多内置转换器,用于常见转换,但也可以完全扩展,以便您可以在类型之间添加自定义转换。 此外,它是泛型感知的。 这意味着,当您在表达式中使用泛型类型时,SpEL会尝试进行转换以维护遇到的任何对象的类型正确性。
实际上这是什么意思? 假设使用setValue()
进行赋值来设置List
属性。 该属性的类型实际上是List <Boolean>
。 SpEL认识到列表中的元素在放入列表之前需要转换为布尔值。 以下示例显示了如何执行此操作:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b is false
Boolean b = simple.booleanList.get(0);
4.1.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
4.1.3。SpEL编译
Spring Framework 4.1包含一个基本的表达式编译器。通常对表达式进行解释,这样可以在评估过程中提供很大的动态灵活性,但不能提供最佳性能。对于偶尔使用表达式,这很好,但是,当与其他组件(例如Spring Integration)一起使用时,性能可能非常重要,并且不需要动态性。
SpEL编译器旨在满足这一需求。
编译最适合类型信息在重复求值时不会改变的表达式。
编译器配置
默认情况下不打开编译器,但是您可以通过两种不同的方式之一来打开它。当SpEL用法嵌入到另一个组件中时,可以使用解析器配置过程或使用系统属性来打开它。
编译器可以在org.springframework.expression.spel.SpelCompilerMode
枚举中捕获的三种模式之一下运行 。
OFF
(默认):编译器关闭。IMMEDIATE
:在IMMEDIATE模式下,将尽快编译表达式。通常是在第一次解释评估之后。如果编译的表达式失败(通常是由于类型更改,如前所述),则表达式求值的调用者将收到异常。MIXED
:在混合模式下,表达式会随着时间静默在解释模式和编译模式之间切换。经过一定数量的解释运行后,它们会切换到编译形式,如果编译形式出了问题(例如,如前面所述的类型更改),则表达式会自动再次切换回解释形式。稍后,它可能会使用生成并使用另一种编译形式。基本上,用户进入IMMEDIATE模式的异常是在内部处理的。
IMMEDIATE之所以存在mode,是因为MIXED mode可能会导致具有副作用的表达式出现问题。如果已编译的表达式在部分成功后就崩溃了,则它可能已经完成了影响系统状态的操作。如果发生这种情况,调用者可能不希望它在解释模式下静默地重新运行,因为表达式的一部分可能运行了两次。
选择模式后,使用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开始,已经有了基本的编译框架。但是,该框架尚不支持编译每种表达式。最初的重点是可能在性能关键型上下文中使用的通用表达式。目前无法编译以下类型的表达式:
- 涉及赋值的表达式
- 依赖转换服务的表达式
- 使用自定义解析器或访问器的表达式
- 使用选择或投影的表达式(Expressions using selection or projection)
将来会编译更多类型的表达。
4.2。Bean定义中的表达式
您可以将SpEL表达式与基于XML或基于注释的配置元数据一起使用来定义BeanDefinition
实例。在这两种情况下,定义表达式的语法均为形式 #{ <expression string> }
。
4.2.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属性,如以下示例所示:
<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 -->
</bean>
4.2.2。注释配置
要指定默认值,可以将@Value
注释放在字段,方法以及方法或构造函数参数上。
下面的示例设置字段变量的默认值:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
以下示例显示了等效的但使用属性设置器方法的示例:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
自动装配的方法和构造函数也可以使用@Value注释,如以下示例所示:
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;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
4.3。语言参考
4.3.1。文字表达式
支持的文字表达式的类型为字符串,数值(int,实数,十六进制),布尔值和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();
数字支持使用负号,指数符号和小数点。默认情况下,使用Double.parseDouble()
解析实数。
4.3.2. Properties, Arrays, Lists, Maps, 和 Indexers
属性名称的首字母允许不区分大小写。数组和列表的内容通过使用方括号表示法获得。
通过在方括号内指定文字键值可以获取Map的内容。
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
4.3.3。内联列表(Inline 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);
{}
本身意味着一个空列表。出于性能原因,如果列表本身完全由固定文字组成,则会创建一个常量列表来表示该表达式(而不是在每次求值时都建立一个新列表)。
4.3.4。内联Map
您也可以使用{key:value}
符号直接在表达式中表达Map
。以下示例显示了如何执行此操作:
// 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
。出于性能原因,如果Map本身由固定的文字或其他嵌套的常量结构(List或Map)组成,则会创建一个常量Map来表示该表达式(而不是在每次求值时都构建一个新的Map)。Map键的引用是可选的。上面的示例不使用带引号的键。
4.3.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);
构造多维数组时,当前无法提供初始化程序。
4.3.6。方法
您可以使用典型的Java编程语法来调用方法。您还可以在文字上调用方法。还支持变量参数。下面的示例演示如何调用方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
4.3.7。运算符
关系运算符
使用标准运算符表示法支持关系运算符(等于,不等于,小于,小于或等于,大于和大于或等于)。
// 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);
对null
的大于和小于比较遵循一个简单的规则:null被视为无(不是零)。 结果,任何其他值始终大于null(X > null
始终为true
),并且其他任何值都不小于零(X < null
始终为false
)。
如果您更喜欢数字比较,请避免使用基于数字的空比较,而建议使用零进行比较(例如,X > 0
或X < 0
)。
除了标准的关系运算符外,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);
请注意原始类型,因为它们会立即被包装为包装器类型,因此,按预期方式,1个instanceof T(int)
的计算结果为false,而1个instanceof T(Integer)的计算结果为true。
每个符号运算符也可以指定为纯字母等效项。这样可以避免使用的符号对于嵌入表达式的文档类型具有特殊含义的问题(例如在XML文档中)。等效的文字是:
- lt(<)
- gt(>)
- le(<=)
- ge(>=)
- eq(==)
- ne(!=)
- div(/)
- mod(%)
- not(!)
所有的文本运算符都不区分大小写。
逻辑运算符
SpEL支持以下逻辑运算符:
- and
- or
- not
// 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);
数学运算符
您可以在数字和字符串上使用加法运算符。您只能对数字使用减法,乘法和除法运算符。您还可以使用模数(%
)和指数幂(^
)运算符。强制执行标准运算符优先级。以下示例显示了正在使用的数学运算符:
// 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
赋值运算符
要设置属性,请使用赋值运算符(=
)。 这通常在对setValue
的调用内完成,但也可以在对getValue
的调用内完成。 下面的清单显示了使用赋值运算符的两种方法:
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
4.3.8。类型(Types)
您可以使用特殊的T
运算符来指定java.lang.Class
(类型)的实例。 静态方法也可以通过使用此运算符来调用。 StandardEvaluationContext
使用TypeLocator
查找类型,而StandardTypeLocator(可以替换)是在了解java.lang
包的情况下构建的。 这意味着对java.lang
中的类型的T()
引用不需要完全限定,但是所有其他类型引用都必须是完全限定的。下面的示例演示如何使用T运算符:
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);
4.3.9。构造函数
您可以使用new
运算符来调用构造函数。除了基本类型(int,float等)和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);
4.3.10。变量
您可以使用#variableName
语法在表达式中引用变量。 通过在EvaluationContext
实现上使用setVariable
方法设置变量。 以下示例显示了如何使用变量:
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变量
始终定义#this
变量,并且引用当前的评估对象(反对解决不合格的引用)。 始终定义#root
变量,并引用根上下文对象。 尽管#this可能随表达式的组成部分的求值而变化,但#root始终引用根。 以下示例说明如何使用#this和#root变量:
// 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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);
4.3.11。函数
您可以通过注册可以在表达式字符串中调用的用户定义函数来扩展SpEL。 该函数通过EvaluationContext
注册。 下面的示例显示如何注册用户定义的函数:
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();
}
}
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);
4.3.12。Bean引用
如果评估上下文已使用bean解析器配置,则可以使用@
符号从表达式中查找bean 。以下示例显示了如何执行此操作:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").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);
4.3.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);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
4.3.14. Elvis 运算符
Elvis运算符是三元运算符语法的简化,并且在 Groovy语言中使用。
ExpressionParser parser = new SpelExpressionParser();
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
您可以使用Elvis运算符在表达式中应用默认值。以下示例显示如何在@Value
表达式中使用Elvis运算符:
@Value("#{systemProperties['pop3.port'] ?: 25}")
pop3.port
如果定义,将注入系统属性,否则将注入25。
4.3.15。安全导航运算符
安全导航运算符用于避免NullPointerException
,它来自Groovy语言。 通常,当您引用一个对象时,可能需要在访问该对象的方法或属性之前验证其是否为null。 为了避免这种情况,安全导航运算符返回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!!!
4.3.16。Collection Selection
Selection 是一种强大的表达式语言功能,可让您通过从源集合中进行选择来将其转换为另一个集合。
选择使用 .?[selectionExpression]
的语法。 它过滤集合并返回一个包含原始元素子集的新集合。
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
在List和Map上都可以选择。对于列表,将针对每个单独的列表元素评估选择标准。针对Map,针对每个Map条目(Java类型的对象)评估选择标准 Map.Entry。每个Map条目都有其键和值,可作为属性访问以供选择。
以下表达式返回一个新Map,该Map由原始地图中条目值小于27的那些元素组成:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回所有选定的元素外,您只能检索第一个或最后一个值。要获得与选择匹配的第一个条目,语法为 .^[selectionExpression]
。要获取最后的匹配选择,语法为 .$[selectionExpression]
。
4.3.17. Collection Projection
Projection使集合可以驱动子表达式的求值,结果是一个新的集合。投影的语法为.![projectionExpression]
。
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
您还可以使用Map来驱动投影,在这种情况下,将根据地图中的每个条目(以Java表示Map.Entry)对投影表达式进行求值。跨Map的投影结果是一个List,其中包含针对每个Map条目的投影表达式的评估。
4.3.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"
通过将文字文本'random number is '与对#{ }
定界符内的表达式进行求值的结果(在本例中为调用random()
方法的结果)来对字符串进行求值。parseExpression()
方法的第二个参数是ParserContext
类型。ParserContext接口用于影响表达式的解析方式,以支持表达式模板功能。定义TemplateParserContext如下:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}