此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

评估

本节介绍了 SpEL 接口及其表达式语言的编程使用。完整的语言参考可以在语言参考中找到。spring-doc.cadn.net.cn

以下代码演示了如何使用 SpEL API 来计算文字字符串 表达Hello World.spring-doc.cadn.net.cn

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.expressionpackage 及其子包,例如spel.support.spring-doc.cadn.net.cn

ExpressionParserinterface 负责解析表达式字符串。 在 前面的示例中,表达式 string 是一个字符串文字,由周围的单引号表示。 这Expression接口负责评估defined 表达式字符串。调用时可以抛出的两种异常parser.parseExpression(…​)exp.getValue(…​)ParseExceptionEvaluationException分别。spring-doc.cadn.net.cn

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

在下面的方法调用示例中,我们调用concat字符串上的方法 字面Hello World.spring-doc.cadn.net.cn

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!".

以下示例演示了如何访问BytesJavaBean 的属性 字符串文字,Hello World.spring-doc.cadn.net.cn

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)以及相应的属性值设置。 也可以访问公共字段。spring-doc.cadn.net.cn

以下示例演示如何使用点表示法获取字符串文字的长度。spring-doc.cadn.net.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.cadn.net.cn

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或使用注册类型转换器进行转换。spring-doc.cadn.net.cn

SpEL 更常见的用法是提供一个被计算的表达式字符串 针对特定对象实例(称为根对象)。以下示例显示 如何检索name属性Inventor课程和作方法 引用name布尔表达式中的属性。spring-doc.cadn.net.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

EvaluationContextAPI 用于计算表达式以解析属性, 方法或字段,并帮助执行类型转换。Spring 提供了两个 实现。spring-doc.cadn.net.cn

SimpleEvaluationContext

公开基本 SpEL 语言功能和配置选项的子集,用于 不需要 SpEL 语言全部范围的表达式类别 语法,并且应该受到有意义的限制。示例包括但不限于 数据绑定表达式和基于属性的筛选器。spring-doc.cadn.net.cn

StandardEvaluationContext

公开了全套 SpEL 语言功能和配置选项。您可以使用 它来指定一个默认的根对象,并配置每个可用的与评估相关的 策略。spring-doc.cadn.net.cn

SimpleEvaluationContext旨在仅支持 SpEL 语言的子集 语法。例如,它排除了 Java 类型引用、构造函数和 bean 引用。 它还要求显式选择对属性和方法的支持级别 在表达式中。创建SimpleEvaluationContext您需要选择级别 SpEL 表达式中数据绑定所需的支持:spring-doc.cadn.net.cn

顺手SimpleEvaluationContext.forReadOnlyDataBinding()启用只读访问 通过以下方式前往属性DataBindingPropertyAccessor.同样地SimpleEvaluationContext.forReadWriteDataBinding()支持对 性能。或者,通过以下方式配置自定义访问器SimpleEvaluationContext.forPropertyAccessors(…​),可能会禁用分配,以及 (可选)通过构建器激活方法解析和/或类型转换器。spring-doc.cadn.net.cn

类型转换

默认情况下,SpEL 使用 Spring 核心中可用的转换服务 (org.springframework.core.convert.ConversionService).此转换服务自带 具有许多用于常见转换的内置转换器,但也可以完全扩展,因此 您可以在类型之间添加自定义转化。此外,它还具有泛型感知功能。 这意味着,当您在表达式中使用泛型类型时,SpEL 会尝试 转换来维护它遇到的任何对象的类型正确性。spring-doc.cadn.net.cn

这在实践中意味着什么?假设赋值,使用setValue(),正在使用 将List财产。属性的类型实际上是List<Boolean>.斯佩尔 认识到列表的元素需要转换为Boolean以前 被放置在其中。以下示例演示了如何执行此作。spring-doc.cadn.net.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 表达式解析器 对象 (org.springframework.expression.spel.SpelParserConfiguration).配置 对象控制某些表达式组件的行为。例如,如果您 index 到集合中,指定索引处的元素为null,SpEL可以 自动创建元素。当使用由 属性引用链。同样,如果您索引到集合中并指定 索引,SpEL 可以自动 扩大集合以适应该索引。为了在 指定索引,SpEL 将尝试使用元素类型的默认值创建元素 构造函数,然后再设置指定值。如果元素类型没有 默认构造函数,null将添加到集合中。如果没有内置 知道如何设置值的转换器或自定义转换器,null将保留在 集合。以下示例演示了如何 自动将List.spring-doc.cadn.net.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 表达式不能包含超过 10,000 个字符;但是,maxExpressionLength是可配置的。如果您创建SpelExpressionParser以编程方式,您可以指定自定义maxExpressionLength创建SpelParserConfiguration您提供给SpelExpressionParser.如果您愿意 将maxExpressionLength用于解析ApplicationContext——例如,在 XML Bean 定义中,@Value等——你可以 设置一个名为 JVM 系统属性或 Spring 属性spring.context.expression.maxLength到应用程序所需的最大表达式长度(请参阅支持的 Spring 属性)。spring-doc.cadn.net.cn

SpEL 编译

Spring 为 SpEL 表达式提供了一个基本的编译器。表达式通常是 解释,这在评估期间提供了很大的动态灵活性,但不会 提供最佳性能。对于偶尔的表达式用法,这很好,但是,当 被其他组件(例如 Spring Integration)使用,性能可能非常重要, 而且没有真正需要这种活力。spring-doc.cadn.net.cn

SpEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为并使用它 类以实现更快的表达式评估。由于缺乏打字 表达式时,编译器使用在解释评估期间收集的信息 执行编译时表达式的。例如,它不知道类型 纯粹来自表达式的属性引用,但在第一次解释期间 评估,它会发现它是什么。当然,基于这样的派生编译 如果各种表达式元素的类型,信息以后可能会造成麻烦 随着时间的推移而变化。因此,编译最适合 类型信息不会在重复求值时更改。spring-doc.cadn.net.cn

考虑以下基本表达式。spring-doc.cadn.net.cn

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问、某些属性取消引用和 数字运算,性能提升可能非常明显。在一个微型示例中 基准运行 50,000 次迭代,使用解释器和 使用表达式的编译版本仅 3 毫秒。spring-doc.cadn.net.cn

编译器配置

默认情况下,编译器未打开,但您可以在以下两个中的任何一个中打开它 不同的方式。您可以使用解析器配置过程将其打开 (讨论过 较早),或者当 SpEL 用法嵌入到另一个 元件。本节讨论这两个选项。spring-doc.cadn.net.cn

编译器可以在三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerMode枚举。模式如下。spring-doc.cadn.net.cn

OFF

编译器已关闭,所有表达式都将以解释模式进行计算。这是默认模式。spring-doc.cadn.net.cn

IMMEDIATE

在即时模式下,表达式会尽快编译,通常是在第一次解释的评估之后。如果编译表达式的计算失败(对于例如,由于类型更改,如前所述),表达式的调用者评估会收到异常。如果各种表达式元素的类型发生变化随着时间的推移,请考虑切换到MIXED模式或关闭编译器。spring-doc.cadn.net.cn

MIXED

在混合模式下,表达式求值会随着时间的推移在解释和编译之间静默切换。在成功运行一定次数的解释后,表达式将被编译。如果编译表达式的求值失败(例如,由于类型更改),则将在内部捕获该失败,并且系统将切换回到给定表达式的解释模式。基本上,调用方在IMMEDIATE模式在内部处理。一段时间后,编译器可能会生成另一个编译形式并切换到它。这种切换的循环在解释模式和编译模式之间将持续下去,直到系统确定它继续尝试是没有意义的——例如,当某个失败阈值已达到时——此时系统将永久切换到解释给定表达式的模式。spring-doc.cadn.net.cn

IMMEDIATE模式存在,因为MIXEDmode 可能会导致表达式出现问题 有副作用。如果编译的表达式在部分成功后爆炸,则它 可能已经做了一些影响系统状态的事情。如果这 ,调用方可能不希望它在解释模式下静默地重新运行, 因为表达式的一部分可以运行两次。spring-doc.cadn.net.cn

选择模式后,使用SpelParserConfiguration以配置解析器。这 以下示例显示了如何执行此作。spring-doc.cadn.net.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)

指定编译器模式时,还可以指定ClassLoader(通过null是允许的)。编译表达式在子ClassLoader在任何 那是提供的。请务必确保,如果ClassLoader指定,它可以 查看表达式评估过程中涉及的所有类型。如果未指定ClassLoader,默认值ClassLoader(通常是上下文ClassLoader为 表达式评估期间正在运行的线程)。spring-doc.cadn.net.cn

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

编译器限制

Spring 不支持编译每种表达式。主要重点是 可能在性能关键型上下文中使用的常见表达式。这 无法编译以下类型的表达式。spring-doc.cadn.net.cn

将来可能会支持编译其他类型的表达式。spring-doc.cadn.net.cn