# 4. 表达式语言

# 4.1.导言

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

EL 用于流中的许多事情,包括:

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

  2. 访问 Web 流中的RequestContext中的数据,例如flowScopecurrentEvent

  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 模式。
你还可以在 Eclipse 中使用 F2(或在其他 IDE 中使用等效的快捷方式)在键入特定的流定义属性时访问可用的文档。

# 4.2.EL 实现

# 4.2.1. Spring El

Web 流使用Spring Expression Language (opens new window)( Spring el)。 Spring 创建 EL 是为了提供一种单一的、支持良好的表达式语言,用于在 Spring 产品组合中的所有产品中使用。在 Spring 框架中,它作为一个单独的 JARorg.springframework.expression分发。

# 4.2.2.统一 El

使用Unified EL (opens new window)还意味着对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 文档中的语言参考 (opens new window)部分。

# 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访问上下文以检索和创建流执行消息,包括错误和成功消息。有关更多信息,请参见MessageContextJavadocs。

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

# 4.4.10.ResourceBundle

使用resourceBundle访问消息资源。

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

# 4.4.11.FlowRequestContext

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

# 4.4.12.FlowExecutionContext

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

# 4.4.13.flowexecutionurl

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

# 4.4.14.ExternalContext

使用externalContext访问客户端环境,包括用户会话属性。有关更多信息,请参见ExternalContextAPI 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