12. 表达式语言 (EL)
Web Flow 使用 EL(表达式语言)来访问其数据模型并调用操作。 本章将帮助您熟悉 EL 语法、配置以及您可以从流程定义中引用的特殊 EL 变量。
EL 在流程中用于许多方面,包括:
-
访问客户端数据,例如声明流输入或引用请求参数。
-
访问 Web Flow 的
RequestContext中的数据,例如flowScope或currentEvent。 -
通过操作调用 Spring 管理对象上的方法。
-
解析表达式,例如状态转换条件、子流程 ID 和视图名称。
EL 还用于将表单参数绑定到模型对象,反之亦然,用于根据模型对象的属性渲染格式化的表单字段。 然而,当使用 Web Flow 和 JSF 时,情况并非如此。 在这种情况下,适用标准的 JSF 组件生命周期。
12.1. 表达式类型
需要理解的一个重要概念是,Web Flow 中有两种类型的表达式:
12.1.1. 标准表达式
最常见的表达式类型是标准表达式。 此类表达式由EL直接计算,不需要用分隔符括起来,例如\#{}。 以下示例展示了这种标准表达式:
<evaluate expression="searchCriteria.nextPage()" />
前述表达式是一个标准表达式,当被求值时,它会调用变量 searchCriteria 上的 nextPage 方法。 如果你尝试将此表达式包含在特殊分隔符中(例如 \#{}),则会得到一个 IllegalArgumentException。 在此上下文中,该分隔符被视为多余。 expression 属性唯一可接受的值是单个表达式字符串。
12.1.2. 模板表达式
第二种表达式是模板表达式。 模板表达式允许将文本字面量与一个或多个标准表达式混合使用。 每个标准表达式块都明确地被\#{}分隔符包围。 以下示例展示了一个模板表达式:
<view-state id="error" view="error-#{externalContext.locale}.xhtml" />
前面的表达式是一个模板表达式。 评估的结果是一个字符串,它将诸如 error- 和 .xhtml 的文本与评估 externalContext.locale 的结果连接起来。 在这里,您需要明确的分隔符来划定模板中的标准表达式块。
| 请参阅 Web Flow XML 模式,以获取接受标准表达式和接受模板表达式的 XML 属性的完整列表。 您还可以在 Eclipse 中使用 F2(或在其他 IDE 中使用等效快捷键)来访问在键入特定流定义属性时的可用文档。 |
12.2. EL实现
Spring Web Flow 支持以下 EL(表达式语言)实现:
12.2.1. Spring 表达式语言 (Spring EL)
Web Flow 使用 Spring 表达式语言 (Spring EL)。Spring EL 的创建旨在为 Spring 产品组合中的所有产品提供一种统一且良好支持的表达式语言。 它作为单独的 jar 包(org.springframework.expression)分布在 Spring 框架中。
12.2.2. 统一表达式语言 (Unified EL)
使用 统一表达式语言(Unified EL) 还意味着对 el-api 的依赖,尽管这通常由您的 web 容器提供。 尽管 Spring EL 是默认且推荐使用的表达式语言,您可以将其替换为统一表达式语言(Unified 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"/>
12.3. EL 可移植性
通常,你会发现 Spring EL 和 Unified EL 的语法非常相似。
然而,Spring El 有一些优势。 例如,Spring EL 与 Spring 3 的类型转换紧密集成,这使得您可以充分利用其特性。 具体来说,目前只有在使用 Spring EL 时才支持泛型类型的自动检测以及格式化注解的使用。
升级到 Spring EL 时,请注意以下与 Unified EL 相关的小改动:
-
在流定义中用
${}分隔的表达式必须更改为\#{}(注意开头的退格字符)。 -
Expressions that test the current event (such as
#{currentEvent == 'submit'}) must be changed to\#{currentEvent.id == 'submit'}(note the addition of theid). -
解析属性(例如
#{currentUser.name})可能会导致NullPointerException,而没有任何检查,例如\#{currentUser != null ? currentUser.name : null}。一个更好的替代方法是安全导航操作符:\#{currentUser?.name}。
有关 Spring EL 语法的更多信息,请参阅 Spring 文档中的语言参考部分。
12.4. 特殊的EL变量
您可以在流程中引用多个隐式变量。
请记住以下通用规则: 只有当您将一个新变量赋值给某个作用域时,才应该使用引用数据作用域的变量(flowScope、viewScope、requestScope 等等)。
例如,当将调用bookingService.findHotels(searchCriteria)的结果赋值给一个名为hotels的新变量时,必须在其前面加上一个作用域变量,以让 Web Flow 知道你想将其存储在哪里。 以下示例展示了如何实现这一点:
<?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>
以下是您可以在流定义中引用的隐式变量列表:
12.4.1. flowScope变量
你可以使用 flowScope 来分配一个流程变量。 流程范围在流程开始时分配,并在流程结束时销毁。 在默认实现中,存储在流程范围中的任何对象都需要可序列化。 以下代码定义了一个 flowScope 变量:
<evaluate expression="searchService.findHotel(hotelId)" result="flowScope.hotel" />
12.4.2. viewScope变量
你可以使用 viewScope 来分配一个视图变量。 视图作用域在进入 view-state 时被分配,并在状态退出时销毁。 视图作用域 仅 能在 view-state 内部引用。 在默认实现中,存储在视图作用域中的任何对象都需要是可序列化的。 以下代码定义了一个 viewScope 变量:
<on-render>
<evaluate expression="searchService.findHotels(searchCriteria)" result="viewScope.hotels"
result-type="dataModel" />
</on-render>
12.4.3. requestScope变量
你可以使用 requestScope 来分配一个请求变量。 请求作用域在调用流时分配,并在流返回时销毁。 以下清单定义了一个 requestScope 变量:
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
12.4.4. flashScope变量
你可以使用 flashScope 来分配一个 flash 变量。 Flash 作用域在流程开始时分配,在每次视图渲染后清除,并在流程结束时销毁。 在默认实现中,存储在 flash 作用域中的任何对象都需要可序列化。 以下代码定义了一个 flashScope 变量:
<set name="flashScope.statusMessage" value="'Booking confirmed'" />
12.4.5. conversationScope变量
您可以使用 conversationScope 来分配一个对话变量。 对话作用域在顶级流程开始时分配,并在顶级流程结束时销毁。 对话作用域由顶级流程及其所有子流程共享。 在默认实现中,对话作用域的对象存储在 HTTP 会话中,通常应可序列化以适应典型的会话复制。 以下清单定义了一个 conversationScope 变量:
<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
12.4.6. requestParameters变量
requestParameters 变量访问客户端请求参数,如下所示:
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
12.4.7. currentEvent变量
变量 currentEvent 访问当前 Event 的属性,如下所示:
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
12.4.8. currentUser变量
变量 currentUser 访问已认证的 Principal,如下所示:
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
12.4.9. messageContext变量
The messageContext 变量访问上下文以检索和创建流执行消息,包括错误和成功消息。 有关更多信息,请参阅 MessageContext Javadocs。 以下示例使用了 messageContext 变量:
<evaluate expression="bookingValidator.validate(booking, messageContext)" />
12.4.10. resourceBundle变量
变量 resourceBundle 访问消息资源,如下所示:
<set name="flashScope.successMessage" value="resourceBundle.successMessage" />
12.4.11. flowRequestContext变量
flowRequestContext 变量访问 RequestContext API,这是当前流请求的表示形式。 有关更多信息,请参阅 API Javadocs。
12.4.12. flowExecutionContext变量
flowExecutionContext 变量访问 FlowExecutionContext API,这是当前流程状态的一个表示形式。 更多信息请参见 API Javadocs。
12.4.14. externalContext变量
The externalContext 变量访问客户端环境,包括用户会话属性。 更多信息请参见 ExternalContext API JavaDocs。 以下示例使用了 externalContext 变量:
<evaluate expression="searchService.suggestHotels(externalContext.sessionMap.userProfile)"
result="viewScope.hotels" />
12.5. 作用域搜索算法
如本节前面所述,在将变量分配到某个流范围时,需要引用该范围。 以下示例展示了如何实现:
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
当您只是访问某个作用域中的变量时,引用该作用域是可选的,如下所示:
<evaluate expression="entityManager.persist(booking)" />
当未指定作用域时,如同之前展示的booking的用法,将会使用一种作用域搜索算法。 该算法会在请求、闪存、视图、流程和会话作用域中查找该变量。 如果找不到这样的变量,则会抛出EvaluationException。