对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
评估
本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在 语言参考 中找到。
以下代码介绍了 SpEL API 来评估文本字符串表达式 .Hello World
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | message 变量的值为 .'Hello World' |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 | message 变量的值为 .'Hello World' |
您最有可能使用的 SPEL 类和接口位于包及其子包中,例如 .org.springframework.expression
spel.support
该接口负责解析表达式字符串。在
前面的示例,表达式 String 是由周围的 single 表示的字符串文本
引号。该接口负责评估先前定义的
expression 字符串。可以引发的两个异常,在调用 和 时,调用 和 ,
分别。ExpressionParser
Expression
ParseException
EvaluationException
parser.parseExpression
exp.getValue
SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在以下方法调用示例中,我们在字符串 Literals 上调用该方法:concat
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | 的值现在是 'Hello World!'。message |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值现在是 'Hello World!'。message |
下面调用 JavaBean 属性的示例调用了该属性:String
Bytes
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 | 此行将 Literals 转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将 Literals 转换为字节数组。 |
SPEL 还通过使用标准点表示法(例如 )以及相应的属性值设置来支持嵌套属性。
还可以访问 Public 字段。prop1.prop2.prop3
以下示例演示如何使用点表示法获取文本的长度:
-
Java
-
Kotlin
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 给出文本的长度。 |
val parser = SpelExpressionParser()
// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 | 'Hello World'.bytes.length 给出文本的长度。 |
可以调用 String 的构造函数,而不是使用字符串文本,如下所示 示例显示:
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 | 从文本构造一个 new 并将其设置为大写。String |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 从文本构造一个 new 并将其设置为大写。String |
请注意泛型方法的使用:.
使用此方法无需将表达式的值强制转换为所需的值
result 类型。如果值无法强制转换为
type 或 converted 使用已注册的类型转换器。public <T> T getValue(Class<T> desiredResultType)
EvaluationException
T
SPEL 更常见的用法是提供一个经过评估的表达式字符串
针对特定对象实例 (称为根对象) 。以下示例显示了
如何从类的实例中检索属性,或者
创建一个布尔条件:name
Inventor
-
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
理解EvaluationContext
在计算表达式以解析时使用该接口
属性、方法或字段,并帮助执行类型转换。Spring 提供两个
实现。EvaluationContext
-
SimpleEvaluationContext
:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SPEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求
U 显式选择对表达式中属性和方法的支持级别。
默认情况下,static 工厂方法仅允许对属性进行读取访问。
您还可以获取构建器来配置所需的确切支持级别,并针对
以下一项或多项组合:create()
-
仅自定义(无反射)
PropertyAccessor
-
用于只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SPEL 使用 Spring 核心中提供的转换服务
().此转换服务随之而来
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转换。此外,它是
generics-aware。这意味着,当您在
表达式中,SPEL 会尝试转换以保持任何对象的类型正确性
它相遇。org.springframework.core.convert.ConversionService
这在实践中意味着什么?假设正在使用 的赋值 ,
以设置属性。属性的类型实际上是 。斯佩尔
识别出列表的元素需要转换为 before
被放置在其中。以下示例显示了如何执行此操作:setValue()
List
List<Boolean>
Boolean
-
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]
解析器配置
可以使用解析器配置来配置 SpEL 表达式解析器
对象 () 。配置
object 控制某些表达式组件的行为。例如,如果你
索引添加到数组或集合中,并且位于指定索引处的元素是 SPEL
可以自动创建元素。当使用由
属性引用链。如果您索引到数组或列表中并指定索引
即超出数组或列表的当前大小的末尾,则 SpEL 可以自动
增大数组或列表以容纳该索引。要在
指定的索引,则 SPEL 将尝试使用元素类型的默认值创建元素
constructor 在设置指定值之前。如果元素类型没有
default 构造函数,将被添加到数组或列表中。如果没有内置的
或知道如何设置值的自定义转换器将保留在数组中,或者
list 的 list 值。以下示例演示了如何自动增长
列表:org.springframework.expression.spel.SpelParserConfiguration
null
null
null
-
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 编译
Spring Framework 4.1 包括一个基本的表达式编译器。表达式通常是 interpreted,这在评估过程中提供了很大的动态灵活性,但 不提供最佳性能。对于偶尔的表达式使用, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且没有真正需要动态性。
SPEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。
请考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。
编译器配置
默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用 Spring 属性 当 SPEL 使用情况嵌入到另一个组件中时。本节讨论 这些选项。
编译器可以在枚举中捕获的三种模式之一运行。模式如下:org.springframework.expression.spel.SpelCompilerMode
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户在 mode 中获得的异常则在内部处理。IMMEDIATE
IMMEDIATE
mode 存在,因为 mode 可能会导致
有副作用。如果编译的表达式在部分成功后崩溃,则
可能已经做了一些影响系统状态的事情。如果此
已发生,调用方可能不希望它在解释模式下静默重新运行。
因为表达式的一部分可能运行两次。MIXED
选择模式后,使用 配置解析器。这
以下示例显示了如何执行此操作:SpelParserConfiguration
-
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)
指定编译器模式时,还可以指定类加载器(允许传递 null)。 编译的表达式在提供的 any 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。
配置编译器的第二种方法是在 SpEL 嵌入到某些
other 组件,并且可能无法通过配置
对象。在这些情况下,可以通过JVM系统属性(或通过SpringProperties
机制)将属性设置为枚举值(、 或 )之一。spring.expression.compiler.mode
SpelCompilerMode
off
immediate
mixed