对于最新的稳定版本,请使用 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
package 及其子软件包,例如spel.support
.
这ExpressionParser
interface 负责解析表达式字符串。在
前面的示例,表达式 String 是由周围的 single 表示的字符串文本
引号。这Expression
interface 负责评估之前定义的
expression 字符串。可以引发的两个异常ParseException
和EvaluationException
,调用parser.parseExpression
和exp.getValue
,
分别。
SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在下面的方法调用示例中,我们调用concat
method 的字符串文本:
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | 的值message 现在是 'Hello World!'。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值message 现在是 'Hello World!'。 |
以下调用 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 还通过使用标准点表示法(例如prop1.prop2.prop3
) 以及相应的属性值设置。
还可以访问 Public 字段。
以下示例演示如何使用点表示法获取文本的长度:
-
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 | 构造一个新的String 从 Literal 中将其设为大写。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 构造一个新的String 从 Literal 中将其设为大写。 |
请注意 generic 方法的使用:public <T> T getValue(Class<T> desiredResultType)
.
使用此方法无需将表达式的值强制转换为所需的值
result 类型。一EvaluationException
如果值无法强制转换为
类型T
或使用已注册的类型转换器进行转换。
SPEL 更常见的用法是提供一个经过评估的表达式字符串
针对特定对象实例 (称为根对象) 。以下示例显示了
如何检索name
属性从Inventor
class 或
创建一个布尔条件:
-
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
这EvaluationContext
接口在计算表达式以解析
属性、方法或字段,并帮助执行类型转换。Spring 提供两个
实现。
-
SimpleEvaluationContext
:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SPEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求
U 显式选择对表达式中属性和方法的支持级别。
默认情况下,create()
static factory method 仅允许对属性进行读取访问。
您还可以获取构建器来配置所需的确切支持级别,并针对
以下一项或多项组合:
-
习惯
PropertyAccessor
仅 (无反射) -
用于只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SPEL 使用 Spring 核心中提供的转换服务
(org.springframework.core.convert.ConversionService
).此转换服务随之而来
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转换。此外,它是
generics-aware。这意味着,当您在
表达式中,SPEL 会尝试转换以保持任何对象的类型正确性
它相遇。
这在实践中意味着什么?假设赋值,使用setValue()
正在使用
要设置List
财产。属性的类型实际上是List<Boolean>
.斯佩尔
识别出需要将 List 的元素转换为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 表达式解析器
对象 (org.springframework.expression.spel.SpelParserConfiguration
).配置
object 控制某些表达式组件的行为。例如,如果你
index 添加到数组或集合中,并且指定索引处的元素为null
、 SPEL
可以自动创建元素。当使用由
属性引用链。如果您索引到数组或列表中并指定索引
即超出数组或列表的当前大小的末尾,则 SpEL 可以自动
增大数组或列表以容纳该索引。要在
指定的索引,则 SPEL 将尝试使用元素类型的默认值创建元素
constructor 在设置指定值之前。如果元素类型没有
default 构造函数、null
将添加到数组或列表中。如果没有内置的
或知道如何设置值的自定义转换器,null
将保留在数组中,或者
list 的 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 编译
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
enum 中。模式如下:
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户进入的异常IMMEDIATE
mode 则在内部处理。
IMMEDIATE
模式存在,因为MIXED
mode 可能会导致表达式出现问题
有副作用。如果编译的表达式在部分成功后崩溃,则
可能已经做了一些影响系统状态的事情。如果此
已发生,调用方可能不希望它在解释模式下静默重新运行。
因为表达式的一部分可能运行两次。
选择模式后,使用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 组件,并且可能无法通过配置
对象。在这些情况下,可以将spring.expression.compiler.mode
属性(或通过 JVM 系统属性的SpringProperties
机制)复制到SpelCompilerMode
枚举值 (off
,immediate
或mixed
).