# 4. 表达式语言
# 4.1.导言
Web Flow 使用 EL 访问其数据模型并调用操作。本章将让你熟悉 EL 语法、配置和你可以从流定义中引用的特殊 EL 变量。
EL 用于流中的许多事情,包括:
访问客户端数据,例如声明流输入或引用请求参数。
访问 Web 流中的
RequestContext
中的数据,例如flowScope
或currentEvent
。通过操作在 Spring 管理的对象上调用方法。
解析表达式,如状态转换条件、子流 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 时,需要记住一些小的更改,如下所示:
在流定义中用
${}
去毛的表达式必须更改为#{}
。测试当前事件
#{currentEvent == 'submit'}
的表达式必须更改为#{currentEvent.id == 'submit'}
。解析诸如
#{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
访问上下文以检索和创建流执行消息,包括错误和成功消息。有关更多信息,请参见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
。