对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
评估
本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在语言参考中找到。
以下代码介绍了 SpEL API 来计算文字字符串表达式Hello World
.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | 消息变量的值为'Hello World' . |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 | 消息变量的值为'Hello World' . |
您最有可能使用的 SpEL 类和接口位于org.springframework.expression
package 及其子包,例如spel.support
.
这ExpressionParser
接口负责解析表达式字符串。在
前面的示例中,表达式 string 是一个字符串文字,由周围的单个表示
引号。这Expression
接口负责评估先前定义的
表达式字符串。可以抛出的两个异常,ParseException
和EvaluationException
,调用parser.parseExpression
和exp.getValue
,
分别。
SpEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在下面的方法调用示例中,我们调用concat
字符串文字上的方法:
-
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 | 此行将文字转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将文字转换为字节数组。 |
SpEL 还支持使用标准点表示法(例如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 | 构造一个新的String 从字面意思开始,并使其成为大写。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 构造一个新的String 从字面意思开始,并使其成为大写。 |
请注意通用方法的使用:public <T> T getValue(Class<T> desiredResultType)
.
使用此方法无需将表达式的值转换为所需的值
结果类型。一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
在计算要解析的表达式时使用 interface
属性、方法或字段,并帮助执行类型转换。Spring 提供了两个
实现。
-
SimpleEvaluationContext
:公开基本 SpEL 语言特性的子集,并且 配置选项,用于不需要完整范围的表达式类别 SpEL 语言语法,并且应该受到有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的筛选器。 -
StandardEvaluationContext
:公开全套 SpEL 语言特性和 配置选项。您可以使用它来指定默认根对象并配置 每一种可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 bean 引用。它还要求
显式选择表达式中属性和方法的支持级别。
默认情况下,create()
静态工厂方法仅允许对属性的读取访问。
您还可以获得构建器来配置所需的确切支持级别,定位
以下一种或某种组合:
-
习惯
PropertyAccessor
仅(无反射) -
只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SpEL 使用 Spring 核心中可用的转换服务
(org.springframework.core.convert.ConversionService
).此转换服务自带
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转化。此外,它是
泛型感知。这意味着,当您在
表达式,SpEL 尝试转换以保持任何对象的类型正确性
它遇到。
这在实践中意味着什么?假设赋值,使用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 表达式解析器
对象 (org.springframework.expression.spel.SpelParserConfiguration
).配置
对象控制某些表达式组件的行为。例如,如果您
index 添加到数组或集合中,并且指定索引处的元素为null
、SpEL
可以自动创建元素。当使用由
属性引用链。如果索引到数组或列表并指定索引
超过数组或列表当前大小的末尾,SpEL 可以自动
增加数组或列表以容纳该索引。为了在
指定索引,SpEL 将尝试使用元素类型的默认值创建元素
构造函数,然后再设置指定值。如果元素类型没有
默认构造函数,null
将被添加到数组或列表中。如果没有内置
或知道如何设置值的自定义转换器,null
将保留在数组中或
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 类,该类在运行时体现表达式行为并使用它 类以实现更快的表达式评估。由于缺乏打字 表达式时,编译器使用在解释评估期间收集的信息 执行编译时表达式的。例如,它不知道类型 纯粹来自表达式的属性引用,但在第一次解释期间 评估,它会发现它是什么。当然,基于这样的派生编译 如果各种表达式元素的类型,信息以后可能会造成麻烦 随着时间的推移而变化。因此,编译最适合 类型信息不会在重复求值时更改。
考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
由于前面的表达式涉及数组访问,因此某些属性取消引用 和数字运算,性能提升可能非常明显。在一个例子中 微基准测试运行 50000 次迭代,使用 解释器,并且仅使用表达式的编译版本仅 3 毫秒。
编译器配置
默认情况下,编译器未打开,但您可以在以下两个中的任何一个中打开它 不同的方式。您可以使用解析器配置过程将其打开 (前面讨论过)或使用 Spring 属性 当 SpEL 用法嵌入到另一个组件中时。本节讨论 这些选项。
编译器可以在三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerMode
枚举。模式如下:
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释评估之后。如果编译后的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在解释和编译之间静默切换 随时间变化的模式。经过一定数量的解释运行后,它们切换到编译 form 和,如果编译的表单出现问题(例如类型更改,如 前面所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译表单并切换到它。基本上 用户进入的异常IMMEDIATE
模式在内部处理。
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)。 编译的表达式在提供的任何下创建的子类加载器中定义。 重要的是要确保,如果指定了类加载器,它可以看到 表达式评估过程。如果未指定类加载器,则使用缺省类加载器 (通常是表达式评估期间运行的线程的上下文类加载程序)。
配置编译器的第二种方法是在将 SpEL 嵌入到某些
其他组件,并且可能无法通过配置来配置它
对象。在这些情况下,可以将spring.expression.compiler.mode
属性通过 JVM 系统属性(或通过SpringProperties
机制)设置为SpelCompilerMode
枚举值 (off
,immediate
或mixed
).