# 4. 表达式语言

## 4.1.导言

Web Flow 使用 EL 访问其数据模型并调用操作。本章将让你熟悉 EL 语法、配置和你可以从流定义中引用的特殊 EL 变量。

EL 用于流中的许多事情，包括：

1. 访问客户端数据，例如声明流输入或引用请求参数。

2. 访问 Web 流中的`RequestContext`中的数据，例如`flowScope`或`currentEvent`。

3. 通过操作在 Spring 管理的对象上调用方法。

4. 解析表达式，如状态转换条件、子流 ID 和视图名称。

EL 还用于将表单参数绑定到模型对象，并从模型对象的属性反向呈现格式化的表单字段。然而，当使用 Web 流与 JSF 一起使用时，这种方法并不适用，在这种情况下，标准的 JSF 组件 Lifecyle 就适用了。

### 4.1.1.表达式类型

要理解的一个重要概念是，在 Web 流中有两种类型的表达式：标准表达式和模板表达式。

#### 标准表达式

第一种也是最常见的一种表达式是*标准表达式*。这样的表达式由 EL 直接求值，不需要用`#{}`这样的分隔符括起来。例如：

```
<evaluate expression="searchCriteria.nextPage()" />
				
```

上面的表达式是一个标准表达式，在计算`searchCriteria`变量时调用`nextPage`方法。如果你试图将这个表达式包含在一个特殊的分隔符中，比如`#{}`，你将得到一个`IllegalArgumentException`。在这种情况下，分隔符被视为多余的。`expression`属性唯一可接受的值是一个表达式字符串。

#### 模板表达式

第二种表达式是*模板表达式*。模板表达式允许将文本与一个或多个标准表达式混合在一起。每个标准表达式块都显式地被`#{}`分隔符包围。例如：

```
<view-state id="error" view="error-#{externalContext.locale}.xhtml" />
				
```

上面的表达式是一个模板表达式。求值的结果将是一个字符串，该字符串将诸如`error-`和`.xhtml`等文字文本与求值`externalContext.locale`的结果连接在一起。如你所见，这里需要显式分隔符来划分模板中的标准表达式块。

|[[note]（images/note.png）|Note|
|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|有关接受标准表达式和接受模板表达式的 XML 属性的完整列表，请参见 Web Flow XML 模式。<br/>你还可以在 Eclipse 中使用 F2（或在其他 IDE 中使用等效的快捷方式）在键入特定的流定义属性时访问可用的文档。|    |

## 4.2.EL 实现

### 4.2.1. Spring El

Web 流使用[Spring Expression Language](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html)（ Spring el）。 Spring 创建 EL 是为了提供一种单一的、支持良好的表达式语言，用于在 Spring 产品组合中的所有产品中使用。在 Spring 框架中，它作为一个单独的 JAR`org.springframework.expression`分发。

### 4.2.2.统一 El

使用[Unified EL](https://en.wikipedia.org/wiki/Unified_Expression_Language)还意味着对`el-api`的依赖，尽管你的 Web 容器通常是*提供*。 Spring 虽然 EL 是默认的和推荐使用的表达式语言，但如果你希望这样做，可以用统一的 EL 替换它。你需要以下 Spring 配置来将`WebFlowELExpressionParser`插入`flow-builder-services`:

```
<webflow:flow-builder-services expression-parser="expressionParser"/>

<bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
    <constructor-arg>
        <bean class="org.jboss.el.ExpressionFactoryImpl" />
    </constructor-arg>
</bean>

```

请注意，如果你的应用程序正在注册自定义转换器，那么确保 WebFloweLexPressionParser 配置了具有这些自定义转换器的转换服务是很重要的。

```
<webflow:flow-builder-services expression-parser="expressionParser" conversion-service="conversionService"/>

<bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
    <constructor-arg>
        <bean class="org.jboss.el.ExpressionFactoryImpl" />
    </constructor-arg>
    <property name="conversionService" ref="conversionService"/>
</bean>

<bean id="conversionService" class="somepackage.ApplicationConversionService"/>

```

## 4.3.EL 便携性

通常，你会发现 Spring EL 和 Unified EL 具有非常相似的语法。

然而，请注意，EL 也有一些优点。例如 Spring EL 与 Spring 3 的类型转换紧密集成，这允许你充分利用其功能。特别是，当前仅在 Spring EL 中支持对泛型类型的自动检测以及对格式注释的使用。

在从 Unified EL 升级到 Spring EL 时，需要记住一些小的更改，如下所示：

1. 在流定义中用`${}`去毛的表达式必须更改为`#{}`。

2. 测试当前事件`#{currentEvent == 'submit'}`的表达式必须更改为`#{currentEvent.id == 'submit'}`。

3. 解析诸如`#{currentUser.name}`之类的属性可能会导致 nullpointerexception，而无需进行诸如`#{currentUser != null ? currentUser.name : null}`之类的检查。一个更好的选择是安全导航操作符`#{currentUser?.name}`。

有关 Spring EL 语法的更多信息，请参阅 Spring 文档中的[语言参考](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-language-ref)部分。

## 4.4.特殊 EL 变量

你可以从流中引用几个隐式变量。这些变量将在本节中讨论。

记住这条通则。引用数据作用域（FlowScope、ViewScope、RequestScope 等）的变量只应在为其中一个作用域分配新变量时使用。

例如，当将调用`bookingService.findHotels(searchCriteria)`的结果分配给一个名为“Hotels”的新变量时，你必须在它的前缀加上一个范围变量，以便让 Web 流知道你希望将它存储在哪里：

```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow" ... >

	<var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" />

	<view-state id="reviewHotels">
		<on-render>
			<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
		</on-render>
	</view-state>

</flow>
			
```

但是，当在下面的示例中设置诸如“SearchCriteria”之类的现有变量时，你可以直接引用该变量，而无需用任何作用域变量对其进行前缀：

```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow" ... >

	<var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" />

	<view-state id="reviewHotels">
		<transition on="sort">
			<set name="searchCriteria.sortBy" value="requestParameters.sortBy" />
		</transition>
	</view-state>

</flow>
			
```

以下是可以在流定义中引用的隐式变量列表：

### 4.4.1.flowscope

使用`flowScope`分配流变量。流作用域在流开始时被分配，在流结束时被销毁。对于默认的实现，存储在流作用域中的任何对象都需要是可序列化的。

```
<evaluate expression="searchService.findHotel(hotelId)" result="flowScope.hotel" />
			
```

### 4.4.2.viewscope

使用`viewScope`分配一个视图变量。当`view-state`进入时，视图作用域被分配，而当状态退出时，视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现，存储在视图作用域中的任何对象都需要是可序列化的。

```
<on-render>
    <evaluate expression="searchService.findHotels(searchCriteria)" result="viewScope.hotels"
              result-type="dataModel" />
</on-render>
			
```

### 4.4.3.RequestScope

使用`requestScope`分配一个请求变量。当一个流被调用时，请求作用域被分配，当流返回时，请求作用域被销毁。

```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
			
```

### 4.4.4.FlashScope

使用`flashScope`分配一个 flash 变量。当一个流开始时，flash 作用域被分配，在每个视图呈现后被清除，当该流结束时被销毁。对于默认的实现，存储在 Flash 作用域中的任何对象都需要是可序列化的。

```
<set name="flashScope.statusMessage" value="'Booking confirmed'" />
			
```

### 4.4.5.ConversationScope

使用`conversationScope`分配一个会话变量。对话范围在顶级流启动时被分配，在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现，对话范围的对象存储在 HTTP会话中，并且通常应该是可序列化的，以考虑典型的会话复制。

```
<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
			
```

### 4.4.6.requestParameters

使用`requestParameters`访问客户端请求参数：

```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
			
```

### 4.4.7.currentEvent

使用`currentEvent`访问当前`Event`的属性：

```
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
			
```

### 4.4.8.currentuser

使用`currentUser`访问经过身份验证的`Principal`:

```
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
          result="flowScope.booking" />
			
```

### 4.4.9.messagecontext

使用`messageContext`访问上下文以检索和创建流执行消息，包括错误和成功消息。有关更多信息，请参见`MessageContext`Javadocs。

```
<evaluate expression="bookingValidator.validate(booking, messageContext)" />
			
```

### 4.4.10.ResourceBundle

使用`resourceBundle`访问消息资源。

```
<set name="flashScope.successMessage" value="resourceBundle.successMessage" />
			
```

### 4.4.11.FlowRequestContext

使用`flowRequestContext`访问`RequestContext`API，这是当前流请求的表示。有关更多信息，请参见 API Javadocs。

### 4.4.12.FlowExecutionContext

使用`flowExecutionContext`访问`FlowExecutionContext`API，这是当前流状态的表示。有关更多信息，请参见 API Javadocs。

### 4.4.13.flowexecutionurl

使用`flowExecutionUrl`访问当前流执行视图状态的上下文相关 URI。

### 4.4.14.ExternalContext

使用`externalContext`访问客户端环境，包括用户会话属性。有关更多信息，请参见`ExternalContext`API Javadocs。

```
<evaluate expression="searchService.suggestHotels(externalContext.sessionMap.userProfile)"
          result="viewScope.hotels" />
			
```

## 4.5.范围搜索算法

正如本节前面提到的，在一个流作用域中分配变量时，需要引用该作用域。例如：

```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
		
```

当只访问其中一个作用域中的变量时，引用该作用域是可选的。例如：

```
<evaluate expression="entityManager.persist(booking)" />
		
```

当没有指定作用域时，就像上面使用`booking`一样，使用作用域搜索算法。该算法将在请求、闪存、视图、流和会话范围中查找变量。如果没有找到这样的变量，将抛出一个`EvaluationException`。