对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cn

评估

本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在 语言参考 中找到。spring-doc.cn

以下代码介绍了 SpEL API 来评估文本字符串表达式 .Hello Worldspring-doc.cn

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.expressionspel.supportspring-doc.cn

该接口负责解析表达式字符串。在 前面的示例,表达式 String 是由周围的 single 表示的字符串文本 引号。该接口负责评估先前定义的 expression 字符串。可以引发的两个异常,在调用 和 时,调用 和 , 分别。ExpressionParserExpressionParseExceptionEvaluationExceptionparser.parseExpressionexp.getValuespring-doc.cn

SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。spring-doc.cn

在以下方法调用示例中,我们在字符串 Literals 上调用该方法:concatspring-doc.cn

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 属性的示例调用了该属性:StringBytesspring-doc.cn

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.prop3spring-doc.cn

以下示例演示如何使用点表示法获取文本的长度:spring-doc.cn

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 的构造函数,而不是使用字符串文本,如下所示 示例显示:spring-doc.cn

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)EvaluationExceptionTspring-doc.cn

SPEL 更常见的用法是提供一个经过评估的表达式字符串 针对特定对象实例 (称为根对象) 。以下示例显示了 如何从类的实例中检索属性,或者 创建一个布尔条件:nameInventorspring-doc.cn

// 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 提供两个 实现。EvaluationContextspring-doc.cn

  • SimpleEvaluationContext:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。spring-doc.cn

  • StandardEvaluationContext:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。spring-doc.cn

SimpleEvaluationContext旨在仅支持 SPEL 语言语法的子集。 它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求 U 显式选择对表达式中属性和方法的支持级别。 默认情况下,static 工厂方法仅允许对属性进行读取访问。 您还可以获取构建器来配置所需的确切支持级别,并针对 以下一项或多项组合:create()spring-doc.cn

类型转换

默认情况下,SPEL 使用 Spring 核心中提供的转换服务 ().此转换服务随之而来 具有许多用于常见转换的内置转换器,但也完全可扩展,因此 您可以在类型之间添加自定义转换。此外,它是 generics-aware。这意味着,当您在 表达式中,SPEL 会尝试转换以保持任何对象的类型正确性 它相遇。org.springframework.core.convert.ConversionServicespring-doc.cn

这在实践中意味着什么?假设正在使用 的赋值 , 以设置属性。属性的类型实际上是 。斯佩尔 识别出列表的元素需要转换为 before 被放置在其中。以下示例显示了如何执行此操作:setValue()ListList<Boolean>Booleanspring-doc.cn

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.SpelParserConfigurationnullnullnullspring-doc.cn

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)使用时, 性能可能非常重要,并且没有真正需要动态性。spring-doc.cn

SPEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。spring-doc.cn

请考虑以下基本表达式:spring-doc.cn

someArray[0].someProperty.someOtherProperty < 0.1

因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。spring-doc.cn

编译器配置

默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用 Spring 属性 当 SPEL 使用情况嵌入到另一个组件中时。本节讨论 这些选项。spring-doc.cn

编译器可以在枚举中捕获的三种模式之一运行。模式如下:org.springframework.expression.spel.SpelCompilerModespring-doc.cn

  • OFF(默认):编译器已关闭。spring-doc.cn

  • IMMEDIATE:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。spring-doc.cn

  • MIXED:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户在 mode 中获得的异常则在内部处理。IMMEDIATEspring-doc.cn

IMMEDIATEmode 存在,因为 mode 可能会导致 有副作用。如果编译的表达式在部分成功后崩溃,则 可能已经做了一些影响系统状态的事情。如果此 已发生,调用方可能不希望它在解释模式下静默重新运行。 因为表达式的一部分可能运行两次。MIXEDspring-doc.cn

选择模式后,使用 配置解析器。这 以下示例显示了如何执行此操作:SpelParserConfigurationspring-doc.cn

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 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。spring-doc.cn

配置编译器的第二种方法是在 SpEL 嵌入到某些 other 组件,并且可能无法通过配置 对象。在这些情况下,可以通过JVM系统属性(或通过SpringProperties机制)将属性设置为枚举值(、 或 )之一。spring.expression.compiler.modeSpelCompilerModeoffimmediatemixedspring-doc.cn

编译器限制

从 Spring Framework 4.1 开始,基本的编译框架就位了。但是,框架 尚不支持编译各种表达式。最初的重点是 可能在性能关键型上下文中使用的常用表达式。以下内容 kinds of expression 目前无法编译:spring-doc.cn

将来将可编译更多类型的表达式。spring-doc.cn