Evaluation
本部分介绍了 SpEL 接口及其表达式语言的编程使用。完整的语言参考可在此找到Language Reference.
This section introduces programmatic use of SpEL’s interfaces and its expression language. The complete language reference can be found in the Language Reference.
下面的代码演示如何使用 SpEL API 来评估文字字符串表达式“Hello World”。
The following code demonstrates how to use the SpEL API to evaluate the literal string
expression, Hello World
.
- Java
-
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); (1) String message = (String) exp.getValue();
1 | The value of the message variable is "Hello World" .
|
2 | The value of the message variable is "Hello World" . |
你最可能使用的 SpEL 类和接口位于 org.springframework.expression 包及其子包(例如 spel.support)中。
The SpEL classes and interfaces you are most likely to use are located in the
org.springframework.expression
package and its sub-packages, such as spel.support
.
ExpressionParser 接口负责解析表达式字符串。在前面的示例中,表达式字符串是一个文字字符串,由周围的单引号表示。Expression 接口负责评估已定义的表达式字符串。调用 parser.parseExpression(…) 和 exp.getValue(…) 时可能抛出的两种类型的异常分别为 ParseException 和 EvaluationException。
The ExpressionParser
interface is responsible for parsing an expression string. In the
preceding example, the expression string is a string literal denoted by the surrounding
single quotation marks. The Expression
interface is responsible for evaluating the
defined expression string. The two types of exceptions that can be thrown when calling
parser.parseExpression(…)
and exp.getValue(…)
are ParseException
and
EvaluationException
, respectively.
SpEL 支持广泛的功能,例如调用方法、访问属性和调用构造函数。
SpEL supports a wide range of features such as calling methods, accessing properties, and calling constructors.
在下面的方法调用示例中,我们对字符串文字“Hello World”调用 concat 方法。
In the following method invocation example, we call the concat
method on the string
literal, Hello World
.
- Java
-
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1) String message = (String) exp.getValue();
1 | The value of message is now "Hello World!" .
|
2 | The value of message is now "Hello World!" . |
以下示例演示如何访问字符串文字“Hello World”的 JavaBean 属性“Bytes”。
The following example demonstrates how to access the Bytes
JavaBean property of the
string literal, Hello World
.
- Java
-
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes()' Expression exp = parser.parseExpression("'Hello World'.bytes"); (1) byte[] bytes = (byte[]) exp.getValue();
1 | This line converts the literal to a byte array.
|
2 | This line converts the literal to a byte array. |
SpEL 还支持使用标准点表示法(例如 prop1.prop2.prop3)以及相应的属性值设置来访问嵌套属性。还可以访问公有字段。
SpEL also supports nested properties by using the standard dot notation (such as
prop1.prop2.prop3
) as well as the corresponding setting of property values.
Public fields may also be accessed.
以下示例演示如何使用点表示法获取字符串文字的长度。
The following example shows how to use dot notation to get the length of a string literal.
- Java
-
ExpressionParser parser = new SpelExpressionParser(); // invokes 'getBytes().length' Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1) int length = (Integer) exp.getValue();
1 | ’Hello World'.bytes.length` gives the length of the literal.
|
2 | ’Hello World'.bytes.length` gives the length of the literal. |
可以调用字符串的构造函数,而不是像以下示例所展示那样使用字符串文本。
The String’s constructor can be called instead of using a string literal, as the following example shows.
- Java
-
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1) String message = exp.getValue(String.class);
1 | Construct a new String from the literal and convert it to upper case.
|
2 | Construct a new String from the literal and convert it to upper case. |
请注意使用泛型方法:public <T> T getValue(Class<T> desiredResultType)。使用此方法消除了将表达式的值强制转换为所需结果类型的需要。如果该值无法强制转换为类型 T 或无法使用已注册的类型转换器进行转换,则会抛出 EvaluationException。
Note the use of the generic method: public <T> T getValue(Class<T> desiredResultType)
.
Using this method removes the need to cast the value of the expression to the desired
result type. An EvaluationException
is thrown if the value cannot be cast to the
type T
or converted by using the registered type converter.
更常见的 SpEL 用法是提供针对特定对象实例(称为根对象)进行评估的表达式字符串。以下示例演示如何从 Inventor 类的实例检索 name 属性,以及如何在布尔表达式中引用 name 属性。
The more common usage of SpEL is to provide an expression string that is evaluated
against a specific object instance (called the root object). The following example shows
how to retrieve the name
property from an instance of the Inventor
class and how to
reference the name
property in a boolean expression.
-
Java
-
Kotlin
// 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
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
Understanding EvaluationContext
在评估表达式以解析属性、方法或字段并帮助执行类型转换时,使用 EvaluationContext 接口。Spring 提供了两种实现。
The EvaluationContext
interface is used when evaluating an expression to resolve
properties, methods, or fields and to help perform type conversion. Spring provides two
implementations.
-
SimpleEvaluationContext
: Exposes a subset of essential SpEL language features and configuration options, for categories of expressions that do not require the full extent of the SpEL language syntax and should be meaningfully restricted. Examples include but are not limited to data binding expressions and property-based filters. -
StandardEvaluationContext
: Exposes the full set of SpEL language features and configuration options. You can use it to specify a default root object and to configure every available evaluation-related strategy.
SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它排除了 Java 类型引用、构造函数和 bean 引用。它还要求你显式地选择表达式中属性和方法的支持级别。默认情况下,create() 静态工厂方法仅启用对属性的读取访问。你还可以获取一个构建器,以配置所需的的确切支持级别,针对以下内容的一个或多个组合。
SimpleEvaluationContext
is designed to support only a subset of the SpEL language syntax.
It excludes Java type references, constructors, and bean references. It also requires
you to explicitly choose the level of support for properties and methods in expressions.
By default, the create()
static factory method enables only read access to properties.
You can also obtain a builder to configure the exact level of support needed, targeting
one or some combination of the following.
-
Custom
PropertyAccessor
only (no reflection) -
Data binding properties for read-only access
-
Data binding properties for read and write
Type Conversion
默认情况下,SpEL 使用 Spring Core 中提供的转换服务 (org.springframework.core.convert.ConversionService)。此转换服务附带了许多常见转换的内置转换器,但也可以完全扩展,以便你可以添加在类型之间进行自定义转换。此外,它是泛型感知的。这意味着,当你使用表达式中的泛型类型时,SpEL 会尝试转换以维护其遇到的任何对象的类型正确性。
By default, SpEL uses the conversion service available in Spring core
(org.springframework.core.convert.ConversionService
). This conversion service comes
with many built-in converters for common conversions, but is also fully extensible so
that you can add custom conversions between types. Additionally, it is generics-aware.
This means that, when you work with generic types in expressions, SpEL attempts
conversions to maintain type correctness for any objects it encounters.
这在实践中意味着什么?假设使用 setValue() 进行的分配用于设置一个 List 属性。此属性的类型实际上是 List<Boolean>。SpEL 识别到列表中的元素在放入列表之前需要转换为 Boolean。以下示例演示如何执行此操作。
What does this mean in practice? Suppose assignment, using setValue()
, is being used
to set a List
property. The type of the property is actually List<Boolean>
. SpEL
recognizes that the elements of the list need to be converted to Boolean
before
being placed in it. The following example shows how to do so.
-
Java
-
Kotlin
class Simple {
public List<Boolean> booleanList = new ArrayList<>();
}
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);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val 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
val b = simple.booleanList[0]
Parser Configuration
你可以使用解析器配置对象 (org.springframework.expression.spel.SpelParserConfiguration) 来配置 SpEL 表达式解析器。此配置对象控制一些表达式组件的行为。例如,如果你索引到数组或集合中,并且指定索引处的元素为 null,那么 SpEL 可以自动创建该元素。这在使用由属性引用链组成的表达式时很有用。如果你索引到数组或列表中并指定超出数组或列表当前大小的索引,那么 SpEL 可以自动增加数组或列表以适应该索引。为了在指定索引处添加一个元素,SpEL 会尝试使用元素类型的默认构造函数创建元素,然后再设置指定的值。如果元素类型没有默认构造函数,那么 null 将被添加到数组或列表中。如果没有内置或自定义转换器知道如何设置值,那么 null 将保留在指定索引处的数组或列表中。以下示例演示如何自动增加该列表。
It is possible to configure the SpEL expression parser by using a parser configuration
object (org.springframework.expression.spel.SpelParserConfiguration
). The configuration
object controls the behavior of some of the expression components. For example, if you
index into an array or collection and the element at the specified index is null
, SpEL
can automatically create the element. This is useful when using expressions made up of a
chain of property references. If you index into an array or list and specify an index
that is beyond the end of the current size of the array or list, SpEL can automatically
grow the array or list to accommodate that index. In order to add an element at the
specified index, SpEL will try to create the element using the element type’s default
constructor before setting the specified value. If the element type does not have a
default constructor, null
will be added to the array or list. If there is no built-in
or custom converter that knows how to set the value, null
will remain in the array or
list at the specified index. The following example demonstrates how to automatically grow
the list.
-
Java
-
Kotlin
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
class Demo {
var list: List<String>? = null
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3]")
val demo = Demo()
val o = expression.getValue(demo)
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
默认情况下,SpEL 表达式不能超过 10,000 个字符;但是, maxExpressionLength
是可配置的。如果你以编程方式创建了 SpelExpressionParser
,则可以在创建提供给 SpelExpressionParser
的 SpelParserConfiguration
时指定自定义 maxExpressionLength
。如果你希望设置用于在 ApplicationContext
内(例如,在 XML bean 定义、 @Value
等)解析 SpEL 表达式的 maxExpressionLength
,则可以将名为 spring.context.expression.maxLength
的 JVM 系统属性或 Spring 属性设置为应用程序所需的表达式最大长度(请参阅 Supported Spring Properties)。
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
maxExpressionLength
is configurable. If you create a SpelExpressionParser
programmatically, you can specify a custom maxExpressionLength
when creating the
SpelParserConfiguration
that you provide to the SpelExpressionParser
. If you wish to
set the maxExpressionLength
used for parsing SpEL expressions within an
ApplicationContext
— for example, in XML bean definitions, @Value
, etc. — you can
set a JVM system property or Spring property named spring.context.expression.maxLength
to the maximum expression length needed by your application (see
Supported Spring Properties).
SpEL Compilation
Spring 为 SpEL 表达式提供了一个基本编译器。表达式通常是解释的,这在评估期间提供了很多动态灵活性,但是不能提供最佳性能。对于偶尔的表达式使用,这很好,但是,当被 Spring Integration 等其他组件使用时,性能就非常重要了,并且实际上不需要动态性。
Spring provides a basic compiler for SpEL expressions. Expressions are usually interpreted, which provides a lot of dynamic flexibility during evaluation but does not provide optimum performance. For occasional expression usage, this is fine, but, when used by other components such as Spring Integration, performance can be very important, and there is no real need for the dynamism.
SpEL 编译器旨在满足此需求。在评估期间,编译器会生成一个 Java 类,该类体现了运行时的表达式行为,并利用该类实现更快速的表达式评估。由于缺乏有关表达式的类型化,因此编译器在执行编译时使用在解释评估表达式期间收集的信息。例如,它不会从表达式中完全了解属性引用的类型,但在首次解释评估期间,它会找出是什么。当然,如果各种表达式元素的类型随时间变化,那么基于此派生信息进行编译可能会在以后造成麻烦。因此,编译最适合表达式类型信息不会在重复评估中发生更改。
The SpEL compiler is intended to address this need. During evaluation, the compiler generates a Java class that embodies the expression behavior at runtime and uses that class to achieve much faster expression evaluation. Due to the lack of typing around expressions, the compiler uses information gathered during the interpreted evaluations of an expression when performing compilation. For example, it does not know the type of a property reference purely from the expression, but during the first interpreted evaluation, it finds out what it is. Of course, basing compilation on such derived information can cause trouble later if the types of the various expression elements change over time. For this reason, compilation is best suited to expressions whose type information is not going to change on repeated evaluations.
考虑以下基本表达式。
Consider the following basic expression.
someArray[0].someProperty.someOtherProperty < 0.1
由于前面的表达式涉及数组访问、某些属性取消引用和数值运算,因此性能增益会很明显。在一个运行 50,000 次迭代的示例微基准中,使用解释器评估需要 75 毫秒,而使用编译版本表达式只需 3 毫秒。
Because the preceding expression involves array access, some property de-referencing, and numeric operations, the performance gain can be very noticeable. In an example micro benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and only 3ms using the compiled version of the expression.
Compiler Configuration
该编译器默认未开启,但是你可以采用两种不同方式中的任何一种开启它。你可以通过使用解析器配置流程 (discussed earlier) 或在 SpEL 用法嵌入到另一个组件时使用 Spring 属性来开启它。本节讨论了这两个选项。
The compiler is not turned on by default, but you can turn it on in either of two different ways. You can turn it on by using the parser configuration process (discussed earlier) or by using a Spring property when SpEL usage is embedded inside another component. This section discusses both of these options.
编译器可以在三种模式之一下运行,这三种模式在 org.springframework.expression.spel.SpelCompilerMode
枚举中捕获。模式如下。
The compiler can operate in one of three modes, which are captured in the
org.springframework.expression.spel.SpelCompilerMode
enum. The modes are as follows.
-
OFF
(default): The compiler is switched off. -
IMMEDIATE
: In immediate mode, the expressions are compiled as soon as possible. This is typically after the first interpreted evaluation. If the compiled expression fails (typically due to a type changing, as described earlier), the caller of the expression evaluation receives an exception. -
MIXED
: In mixed mode, the expressions silently switch between interpreted and compiled mode over time. After some number of interpreted runs, they switch to compiled form and, if something goes wrong with the compiled form (such as a type changing, as described earlier), the expression automatically switches back to interpreted form again. Sometime later, it may generate another compiled form and switch to it. Basically, the exception that the user gets inIMMEDIATE
mode is instead handled internally.
IMMEDIATE
模式存在,因为 MIXED
模式可能导致具有副作用的表达式的出现问题。如果编译后的表达式在部分成功后爆炸,它可能已经做了某些事情,影响了系统状态。如果发生了这种情况,调用者可能不希望它在解释模式下静默地重新运行,因为表达式的部分可能会被运行两次。
IMMEDIATE
mode exists because MIXED
mode could cause issues for expressions that
have side effects. If a compiled expression blows up after partially succeeding, it
may have already done something that has affected the state of the system. If this
has happened, the caller may not want it to silently re-run in interpreted mode,
since part of the expression may be run twice.
在选择模式后,使用 SpelParserConfiguration
来配置解析器。以下示例演示了如何执行此操作。
After selecting a mode, use the SpelParserConfiguration
to configure the parser. The
following example shows how to do so.
-
Java
-
Kotlin
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);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
当你指定编译器模式时,你还可以指定 ClassLoader
(允许传递 null
)。编译后的表达式在指定的任何子 ClassLoader
下定义。重要的是确保,如果指定了 ClassLoader
,则它可以查看表达式评估过程中涉及的所有类型。如果你不指定 ClassLoader
,则使用默认 ClassLoader
(通常为在表达式评估期间运行的线程的上下文 ClassLoader
)。
When you specify the compiler mode, you can also specify a ClassLoader
(passing null
is allowed). Compiled expressions are defined in a child ClassLoader
created under any
that is supplied. It is important to ensure that, if a ClassLoader
is specified, it can
see all the types involved in the expression evaluation process. If you do not specify a
ClassLoader
, a default ClassLoader
is used (typically the context ClassLoader
for
the thread that is running during expression evaluation).
配置编译器的第二种方式适用于 SpEL 嵌入到另一个组件中并且不能通过配置对象对其进行配置的情况。在这种情况下,可以使用 JVM 系统属性(或通过 SpringProperties
机制)将 spring.expression.compiler.mode
属性设置为 SpelCompilerMode
枚举值之一(off
、immediate
或 mixed
)。
The second way to configure the compiler is for use when SpEL is embedded inside some
other component and it may not be possible to configure it through a configuration
object. In such cases, it is possible to set the spring.expression.compiler.mode
property via a JVM system property (or via the
SpringProperties
mechanism) to one of the
SpelCompilerMode
enum values (off
, immediate
, or mixed
).
Compiler Limitations
Spring 不支持编译每一种类型的表达式。主要关注的是可能在性能关键上下文中使用的常见表达式。以下类型的表达式无法编译。
Spring does not support compiling every kind of expression. The primary focus is on common expressions that are likely to be used in performance-critical contexts. The following kinds of expressions cannot be compiled.
-
Expressions involving assignment
-
Expressions relying on the conversion service
-
Expressions using custom resolvers
-
Expressions using overloaded operators
-
Expressions using array construction syntax
-
Expressions using selection or projection
将来可能会支持编译其他类型的表达式。
Compilation of additional kinds of expressions may be supported in the future.