el.md 11.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
# 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`