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

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

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