- 5.1. Evaluation
- 5.2. Expressions in Bean Definitions
-
5.3. Language Reference
- 5.3.1. Literal Expressions
- 5.3.2. Properties, Arrays, Lists, Maps, and Indexers
- 5.3.3. Inline Lists
- 5.3.4. Inline Maps
- 5.3.5. Array Construction
- 5.3.6. Methods
- 5.3.7. Operators
- 5.3.8. Types
- 5.3.9. Constructors
- 5.3.10. Variables
- 5.3.11. Functions
- 5.3.12. Bean References
- 5.3.13. Ternary Operator (If-Then-Else)
- 5.3.14. The Elvis Operator
- 5.3.15. Safe Navigation Operator
- 5.3.16. Collection Selection
- 5.3.17. Collection Projection
- 5.3.18. Expression templating
- 5.4. Classes Used in the Examples
Spring表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。
语言语法类似于统一EL,但提供了其他特性,最明显的是方法调用和基本的字符串模板功能。
虽然还有其他几种可用的Java表达式语言——比如OGNL、MVEL和JBoss EL——但创建Spring表达式语言是为了向Spring社区提供一种受良好支持的表达式语言,它可以跨Spring组合中的所有产品使用。
它的语言特性是由Spring组合中的项目需求驱动的,包括Spring Tools for Eclipse中对代码完成支持的工具需求。
也就是说,SpEL基于一种与技术无关的API,该API允许在需要时集成其他表达式语言实现。
虽然SpEL是Spring组合中表达式计算的基础,但它并不直接与Spring绑定,可以独立使用。
为了自成一体,本章中的许多示例使用SpEL,就好像它是一种独立的表达式语言。
这需要创建一些引导基础结构类,比如解析器。
大多数Spring用户不需要处理这个基础设施,相反,可以只编写表达式字符串进行计算。
这种典型用法的一个例子是将SpEL集成到创建XML或基于注释的bean定义中,如定义bean定义的表达式支持所示。
本章涵盖了表达式语言的特性、API和语法。
在一些地方,' Inventor '和' Society '类被用作表达式求值的目标对象。
本章的最后列出了这些类声明和用于填充它们的数据。
表达式语言支持以下功能:
- Literal expressions(文字表达方式)
- Boolean and relational operators(布尔和关系运算符)
- Regular expressions(正则表达式)
- Class expressions(类表达式)
- Accessing properties, arrays, lists, and maps(访问属性、数组、列表和映射)
- Method invocation(方法调用)
- Relational operators(关系运算符)
- Assignment(赋值)
- Calling constructors(调用构造函数)
- Bean references(Bean的引用)
- Array construction(阵列结构)
- Inline lists(内联列表)
- Inline maps(内联映射)
- Ternary operator(三元运算符)
- Variables(变量)
- User-defined functions(用户定义函数)
- Collection projection(投影集合)
- Collection selection(选择集合)
- Templated expressions(模板化表达式)
5.1. Evaluation
本节介绍SpEL接口及其表达式语言的简单使用。完整的语言参考可以在语言参考中找到。
下面的代码引入了SpEL API来计算字符串字面表达式' Hello World '。
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
The value of the message variable is 'Hello World'
.
您最可能使用的SpEL类和接口位于' org.spring框架中。
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();
The value of message
is now 'Hello World!'.
下面调用JavaBean属性的例子调用了' String '属性' Bytes ':
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
This line converts the literal to a byte array.
SpEL还通过使用标准点表示法(如' prop1.prop2.prop3 ')和相应的属性值设置来支持嵌套属性。
也可以访问公共字段。
下面的例子展示了如何使用点表示法来获取文字的长度:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
'Hello World'.bytes.length
gives the length of the literal.
可以调用String的构造函数而不是使用字符串字面值,如下例所示:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
Construct a new String
from the literal and make it be upper case.
注意泛型方法的使用:' public T getValue(Class desireddresulttype) '。
使用此方法无需将表达式的值强制转换为所需的结果类型。
如果值不能转换为类型' 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
5.1.1. Understanding EvaluationContext
“EvaluationContext”接口用于对表达式求值,以解析属性、方法或字段,并帮助执行类型转换。
Spring提供了两种实现。
-
“SimpleEvaluationContext”:公开基本SpEL语言特性和配置选项的子集,用于不需要SpEL语言语法的全部范围并且应该有意义地加以限制的表达式类别。
示例包括但不限于数据绑定表达式和基于属性的过滤器。 -
' standardvaluationcontext ':公开完整的SpEL语言特性和配置选项集。
您可以使用它来指定默认根对象,并配置每个可用的与评估相关的策略。
' SimpleEvaluationContext '被设计为只支持SpEL语言语法的一个子集。
它排除了Java类型引用、构造函数和bean引用。它还要求您显式地选择对表达式中的属性和方法的支持级别。
默认情况下,' create() '静态工厂方法只允许对属性的读访问。
您还可以获得一个构建器来配置所需的精确支持级别,目标是以下一种或某种组合:
- 只自定义“PropertyAccessor”(没有反射)
- 只读访问的数据绑定属性
- 读写数据绑定属性
Type Conversion
默认情况下,SpEL使用Spring core中提供的转换服务(' org.springframework.core.convert.ConversionService ')。这个转换服务附带了许多用于常见转换的内置转换器,但它也是完全可扩展的,因此您可以添加类型之间的自定义转换。此外,它是泛型感知的。这意味着,当您在表达式中使用泛型类型时,SpEL会尝试转换以维护它遇到的任何对象的类型正确性。
这在实践中意味着什么?
假设赋值使用' setValue() '用于设置' List '属性。属性的类型实际上是' List '。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);
5.1.2. Parser Configuration(解析器配置)
可以通过使用解析器配置对象(' org.springframework.expression. spell . spelparserconfiguration ')来配置SpEL表达式解析器。配置对象控制一些表达式组件的行为。例如,如果您在一个数组或集合中建立索引,而指定索引处的元素是' null ', SpEL可以自动创建该元素。这在使用由属性引用链组成的表达式时非常有用。如果在数组或列表中建立索引,并指定超出数组或列表当前大小末尾的索引,SpEL可以自动增加数组或列表以适应该索引。为了在指定的索引处添加元素,在设置指定的值之前,SpEL将尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认构造函数,' null '将被添加到数组或列表中。如果没有内置或自定义转换器知道如何设置值,' 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
5.1.3. SpEL Compilation(DpEL 编译)
Spring Framework 4.1包含一个基本的表达式编译器。
表达式通常是解释的,这在计算期间提供了很多动态灵活性,但不能提供最佳性能。
对于偶尔的表达式使用,这很好,但是,当被其他组件(如Spring Integration)使用时,性能可能非常重要,并且实际上不需要动态性。
SpEL编译器旨在解决这一需求。在求值期间,编译器生成一个Java类,该类包含运行时的表达式行为,并使用该类实现更快的表达式求值。由于表达式周围缺少键入,编译器在执行编译时使用表达式解释计算期间收集的信息。例如,它并不完全从表达式中知道属性引用的类型,但是在第一次解释求值期间,它发现了它是什么。当然,如果各种表达式元素的类型随着时间的推移而变化,基于这些派生信息进行编译可能会导致麻烦。因此,编译最适合于类型信息不会因重复求值而改变的表达式。
考虑下面的基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
因为前面的表达式涉及数组访问、一些属性反引用和数字操作,所以性能的提高是非常明显的。
在一个50000次迭代的微基准测试运行示例中,使用解释器计算需要75ms,而使用编译版本的表达式只需要3ms。
Compiler Configuration(编译器配置)
默认情况下编译器不是打开的,但是您可以通过两种不同的方式打开它。
您可以通过使用解析器配置过程(前面讨论过)或当SpEL用法嵌入到另一个组件中时使用Spring属性来打开它。本节将讨论这两个选项。
编译器可以以三种模式中的一种来操作,这些模式在org.springframework.expression.spel.SpelCompilerMode的枚举中被捕获。模式如下:
- ' OFF '(默认):关闭编译器。
- “IMMEDIATE”:在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”属性通过一个JVM系统属性(或通过(“SpringProperties” appendix-spring-properties机制)的‘SpelCompilerMode枚举值(“off”、“imediate”或“mixed”)。
Compiler Limitations(编译器限制)
从Spring Framework 4.1开始,基本的编译框架已经就位。然而,该框架还不支持编译每一种表达式。最初的重点是可能在性能关键的上下文中使用的通用表达式。以下几种表达式目前还不能编译:
-
赋值的表达式
-
依赖转换服务的表达式
-
使用自定义解析器或访问器的表达式
-
使用选择或投影的表达式
将来会有更多类型的表达式可编译。
5.2. Expressions in Bean Definitions
Bean定义中的表达式
您可以使用SpEL表达式和基于xml或基于注释的配置元数据来定义“BeanDefinition”实例。
在这两种情况下,定义表达式的语法形式都是#{<expression string>}
。
5.2.1. XML Configuration
属性或构造函数参数值可以通过使用表达式设置,如下例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
应用程序上下文中的所有bean都可以作为具有公共bean名称的预定义变量使用。这包括用于访问运行时环境的标准上下文bean,如' environment '(类型为' org.springframework.core.env.Environment '),以及' systemProperties '和' systemEnvironment '(类型为' Map<String, Object> ')。
下面的示例显示了对' systemProperties ' bean的SpEL变量访问:
<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>
5.2.2. Annotation Configuration
要指定默认值,可以在字段、方法和方法或构造函数参数上放置“@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;
}
}
下面的例子展示了一个等价的属性setter方法:
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;
}
// ...
}
5.3. Language Reference
本节描述Spring表达式语言的工作原理。它涵盖以下主题:
- Literal Expressions
- Properties, Arrays, Lists, Maps, and Indexers
- Inline Lists
- Inline Maps
- Array Construction
- Methods
- Operators
- Types
- Constructors
- Variables
- Functions
- Bean References
- Ternary Operator (If-Then-Else)
- The Elvis Operator
- Safe Navigation Operator
5.3.1. Literal Expressions
支持的文字表达式类型有字符串、数字值(int、real、hex)、布尔值和空值。字符串由单引号分隔。若要将单引号本身放入字符串中,请使用两个单引号字符。
下面的清单显示了文字的简单用法。通常,它们不会像这样单独使用,而是作为更复杂表达式的一部分使用——例如,在逻辑比较运算符的一侧使用文字。
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() '解析实数。
5.3.2. Properties, Arrays, Lists, Maps, and Indexers
使用属性引用导航很容易。为此,使用句点来指示嵌套的属性值。“Inventor”类的“pupin”和“tesla”的实例使用示例中使用的类部分中列出的数据进行填充。
为了在对象图中“向下”导航,得到Tesla的出生年份和Pupin的出生城市,我们使用以下表达式:
// evals to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
注意:
允许属性名称的第一个字母不区分大小写。因此,上面例子中的表达式可以分别地写成“Birthdate.Year + 1900和 PlaceOfBirth.City
。此外,可以通过方法调用访问属性——例如,' getPlaceOfBirth(). getcity() '而不是' placeOfBirth.city '。
使用方括号表示法获取数组和列表的内容,示例如下:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Inventions Array
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Members List
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
context, ieee, String.class);
映射的内容是通过在括号中指定字面键值来获得的。在下面的例子中,因为' officers '映射的键是字符串,所以我们可以指定字符串字面值:
// 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");
5.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);
'{} '本身意味着一个空列表。出于性能原因,如果列表本身完全由固定的字面值组成,则创建一个常量列表来表示表达式(而不是在每次求值时构建一个新列表)。
5.3.4. Inline 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键的引用是可选的(除非键包含句号(' . '))。上面的例子没有使用引号键。
5.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);
在构造多维数组时,目前无法提供初始化式。
5.3.6. Methods
您可以使用典型的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);
5.3.7. Operators
Spring表达式语言支持以下类型的操作符:
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);
注意:
对' null '进行大于和小于的比较遵循一个简单的规则:' null '被视为无(即不为零)。因此,任何其他值总是大于' null ' (' X > null '总是' true '),没有其他值小于零(' X < null '总是' false ')。如果您更喜欢数值比较,请避免基于数字的' null '比较,而倾向于与0进行比较(例如,' 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
(!
).
所有的文本操作符都是不区分大小写的。
Logical Operators
SpEL支持以下逻辑操作符:
-
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
The Assignment Operator
要设置属性,请使用赋值操作符(' = ')。这通常是在调用' 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);
5.3.8. Types
你可以使用特殊的' T '操作符来指定一个' java.lang.Class '(类型)的实例。静态方法也可以通过使用此操作符来调用。StandardEvaluationContext使用TypeLocator来查找类型,而StandardTypeLocator(可以替换)是基于对java.lang包的理解构建的。
这意味着T()引用java.lang包中的类型不需要完全限定,但所有其他类型引用必须是完全限定的。
下面的示例演示如何使用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);
5.3.9. Constructors
你可以使用“new”操作符来调用构造函数。你应该对所有类型使用完全限定类名,除了那些位于' java.lang ' package (' Integer ', ' Float ', ' String ',等等)。下面的例子展示了如何使用“new”操作符来调用构造函数:
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
5.3.10. Variables
可以使用' #variableName '语法引用表达式中的变量。变量是通过在“EvaluationContext”实现上使用“setVariable”方法设置的。
注意:
有效的变量名必须由下列一个或多个受支持的字符组成。
-
字母:' A '到' Z '和' A '到' Z '
-
数字:' 0 '到' 9 '
-
下划线:' _
-
'美元符号:' $ '
下面的例子展示了如何使用变量。
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"
The #this
and #root
Variables
' #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);
5.3.11. Functions
可以通过注册可以在表达式字符串中调用的用户定义函数来扩展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);
5.3.12. Bean References
如果计算上下文已经配置了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);
5.3.13. Ternary Operator (If-Then-Else)
可以使用三元运算符在表达式中执行if-then-else条件逻辑。下面的清单显示了一个最小的示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
在这种情况下,布尔值'false '导致返回字符串值'false exp ' '。下面是一个更现实的例子:
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"
有关三元运算符的更简短的语法,请参阅下一节的Elvis运算符。
5.3.14. The Elvis Operator
Elvis操作符是三元操作符语法的缩写,在Groovy语言中使用。对于三元运算符语法,你通常需要重复一个变量两次,如下例所示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,您可以使用Elvis操作符(因与Elvis的发型相似而命名)。下面的示例演示如何使用Elvis操作符:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
下面的清单显示了一个更复杂的示例:
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。如果没有定义,则为25。
5.3.15. Safe Navigation Operator
安全导航操作符用于避免“NullPointerException”,它来自Groovy语言。通常,当您有一个对象的引用时,您可能需要在访问对象的方法或属性之前验证它不是空的。为了避免这种情况,安全导航操作符返回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!!!
5.3.16. Collection Selection
选择是一个强大的表达式语言特性,它允许您通过从一个源集合的条目中进行选择,将其转换为另一个集合。
选择使用' .?[selectionExpression] '的语法。
它过滤集合并返回包含原始元素子集的新集合。
例如,选择让我们很容易获得塞尔维亚发明者的列表,如下例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext);
数组和任何实现' java.lang.Iterable’或‘java.util.Map” '的东西都支持选择。
对于列表或数组,将根据每个单独的元素计算选择条件。
对于一个映射,选择标准根据每个映射条目(Java类型‘map . entry’的对象)进行评估。
每个映射条目都有其可访问的' key '和' value '属性,以便在选择中使用。
下面的表达式返回一个新映射,该映射由原映射的元素组成,其中条目的值小于27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
除了返回所有选中的元素外,您只能检索第一个或最后一个元素。
要获得与选择匹配的第一个元素,语法是' .[1] '。
要获得最后一个匹配的选择,语法是' .$[selectionExpression] '。
5.3.17. Collection Projection
投影让集合驱动子表达式的求值,结果是一个新的集合。
投影的语法是' .![projectionExpression] '。
例如,假设我们有一份发明家名单,但想要他们出生的城市名单。
实际上,我们想要评估出生地点。
在发明者名单上的每一个条目上。
下面的示例使用投影来实现:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
数组和任何实现' java.lang.Iterable’或‘java.util.Map” '的东西都支持投影。
当使用映射来驱动投影时,投影表达式根据映射中的每个条目进行计算(表示为Java的“map . entry”)。
跨映射的投影结果是一个列表,其中包含对每个映射条目的投影表达式的求值。
5.3.18. 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"
字符串的计算方法是将文本“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;
}
}
5.4. Classes Used in the Examples
本节列出了本章示例中使用的类。
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;
}
}
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;
}
}
package org.spring.samples.spel.inventor;
import java.util.*;
public class Society {
private String name;
public static String Advisors = "advisors";
public static String President = "president";
private List<Inventor> members = new ArrayList<Inventor>();
private Map officers = new HashMap();
public List getMembers() {
return members;
}
public Map getOfficers() {
return officers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMember(String name) {
for (Inventor inventor : members) {
if (inventor.getName().equals(name)) {
return true;
}
}
return false;
}
}
-
selectionExpression ↩︎