# 4.1求值

本节介绍了SPEL接口及其表达式语言的简单用法。完整的语言引用可以在语言引用中找到。

下面的代码介绍了用于计算文本字符串表达式hello world的spel api。

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();
```

> message的值是“Hello World”。

你最可能使用的spel类和接口位于org.springframework.expression包及其子包（如spel.support）中。

ExpressionParser接口负责分析表达式字符串。在前面的示例中，表达式字符串是由周围的单引号表示的字符串文字。表达式接口负责计算之前定义的表达式字符串。分别调用parser.parseExpression和exp.getValue时，可以引发两个异常：parseException和evaluationException。

SPEL支持广泛的特性，例如调用方法、访问属性和调用构造函数。

在下面的方法调用示例中，我们在字符串文本上调用concat方法：

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 
String message = (String) exp.getValue();
```

> 现在message的值是'Hello World!'.

以下调用JavaBean属性的示例调用String属性Bytes：

```
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); 
byte[] bytes = (byte[]) exp.getValue();
```

> 字符串被转换为byte array。

spel还通过使用标准点表示法（如prop1.prop2.prop3）以及相应的属性值设置来支持嵌套属性。也可以访问公共字段。

下面的示例演示如何使用点表示法获取文本的长度：

```
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 
int length = (Integer) exp.getValue();
```

可以调用字符串的构造函数，而不是使用字符串文字，如下示例所示：

```
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 
String message = exp.getValue(String.class);
```

注意泛型方法的用法：public\<T>T getValue（class\<T> desiredResultType）。使用此方法可以消除将表达式值强制转换为所需结果类型的需要。如果无法将值强制转换为T类型或无法使用注册的类型转换器转换，则将引发EvaluationException。

spel的更常见用法是提供一个表达式字符串，该字符串根据特定的对象实例（称为根对象）进行计算。以下示例显示如何从Inventor类的实例中检索name属性或创建布尔条件：

```
// 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"); 
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
```

## 4.1.1 理解EvaluationContext

当计算表达式以解析属性、方法或字段并帮助执行类型转换时，将使用EvaluationContext接口。Spring提供了两种实现。

* SimpleEvaluationContext:为不需要完整的SPEL语言语法并且应受到有意义限制的表达式类别公开基本的SPEL语言功能和配置选项的子集。示例包括但不限于数据绑定表达式和基于属性的筛选器。
* StandardEvaluationContext：公开全套SPEL语言功能和配置选项。你可以使用它来指定一个默认的根对象，并配置每个可用的与评估相关的策略。

SimpleEvaluationContext设计为仅支持SPEL语言语法的一个子集。它不包括Java类型引用、构造函数和bean引用。它还要求你显式选择表达式中属性和方法的支持级别。默认情况下，create（）静态工厂方法仅启用对属性的读取访问。你还可以获得一个构建器来配置所需的确切支持级别，针对以下一种或几种组合：

* 仅自定义PropertyAccessor（无反射）
* 只读访问的数据绑定属性
* 读写数据绑定属性

**类型转换**

默认情况下，spel使用SpringCore中提供的转换服务（org.springframework.core.convert.conversionService）。此转换服务附带许多用于常见转换的内置转换器，但也完全可扩展，因此你可以在类型之间添加自定义转换。此外，它还是generics-aware的。这意味着，当你在表达式中处理泛型类型时，spel会尝试转换来维护它遇到的任何对象的类型正确性。

这在实践中意味着什么？假设使用setValue（）进行的赋值用于设置List属性。属性的类型实际上是list\<boolean>。spel认识到列表中的元素在放入之前需要转换为布尔值。以下示例显示了如何执行此操作：

```
class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

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);
```

## 4.1.2 解析配置

可以使用解析器配置对象（org.springframework.expression.spel.SpelParserConfiguration）配置SPEL表达式解析器。配置对象控制一些表达式组件的行为。例如，如果索引到数组或集合，并且指定索引处的元素为空，则可以自动创建元素。这在使用由属性引用链组成的表达式时很有用。如果索引到数组或列表，并指定超出当前数组或列表大小末尾的索引，则可以自动增大数组或列表以容纳该索引。下面的示例演示如何自动增大列表：

```
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
```

## 4.1.3. SpEL编译

SpringFramework4.1包括一个基本表达式编译器。通常对表达式进行解释，这在评估期间提供了很大的动态灵活性，但不能提供最佳性能。对于偶尔使用的表达式用法，这是可以的，但是，当被其他组件（如Spring集成）使用时，性能可能非常重要，并且不需要动态性。

spel编译器旨在满足这一需求。在求值过程中，编译器生成一个Java类，该类在运行时体现表达式行为，并使用该类实现更快的表达式求值。由于没有在表达式周围键入内容，编译器在执行编译时使用在表达式的解释性计算期间收集的信息。例如，它不完全从表达式中知道属性引用的类型，但在第一个解释的计算过程中，它会发现它是什么。当然，如果各种表达式元素的类型随着时间的推移而改变，那么基于此类派生信息进行编译可能会在以后引起麻烦。因此，编译最适合于类型信息在重复计算时不会更改的表达式。

考虑以下基本表达式：

```
someArray[0].someProperty.someOtherProperty < 0.1
```

由于前面的表达式涉及数组访问、一些属性取消引用和数值操作，因此性能提高可能非常明显。在一个50000次迭代的微基准测试运行的例子中，使用解释器评估花费了75毫秒，而使用已编译的表达式版本只花费了3毫秒。

**编译器配置**

默认情况下不会打开编译器，但你可以通过两种不同的方式之一打开它。你可以通过使用解析器配置过程（前面讨论过）或在另一个组件中嵌入spel用法时使用系统属性来启用它。本节讨论这两个选项。

编译器可以在三种模式中的一种模式下运行，这些模式在org.springframework.expression.spel.SpelCompilerMode枚举中列举的。模式如下：

* 关闭（默认）：编译器关闭。
* IMMEDIATE：在immediate模式下，尽快编译表达式。这通常是在第一次解释评估之后。如果编译表达式失败（通常是由于前面描述的类型更改），则表达式计算的调用方将收到异常。
* MIXED：在混合模式下，表达式会随着时间的推移在解释和编译模式之间自动切换。在经过一定数量的解释运行后，它们将切换到已编译的表单，如果编译的表单出现问题（如前面所述的类型更改），表达式将自动再次切换回已解释的表单。稍后，它可能会生成另一个已编译的表单并切换到该表单。基本上，用户进入IMMEDIATE模式的异常是在内部处理的。

IMMEDIATE模式存在，因为MIXED模式可能会导致具有副作用的表达式出现问题。如果编译表达式在部分成功后报异常，它可能已经做了一些影响系统状态的事情。如果发生了这种情况，调用方可能不希望它以解释模式静默地重新运行，因为表达式的一部分可能运行了两次。

选择模式后，使用SpelParserConfiguration配置解析器。以下示例显示了如何执行此操作：

```
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);
```

指定编译器模式时，还可以指定类加载器（允许传递空值）。编译表达式在任何提供的子类加载器下创建的子类加载器中定义。必须确保，如果指定了类加载器，它可以看到表达式计算过程中涉及的所有类型。如果不指定类加载器，则使用默认的类加载器（通常是表达式计算期间运行的线程的上下文类加载器）。

配置编译器的第二种方法是当spel嵌入到其他组件中，并且可能无法通过配置对象对其进行配置时使用。在这些情况下，可以使用系统属性。可以将spring.expression.compiler.mode属性设置为SpelCompilerMode枚举值之一（off、immediate或mixed）。

**Compiler限制**

从SpringFramework4.1开始，基本的编译框架就已经就位了。然而，框架还不支持编译各种类型的表达式。最初的重点是可能在性能关键的上下文中使用的公共表达式。目前无法编译以下类型的表达式：

* 涉及赋值的表达式
* 依赖转换服务的表达式
* 使用自定义冲突解决程序或访问器的表达式
* 使用选择或投影的表达式

将来会编译更多类型的表达式。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/4.spel/4.1evaluation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
