12. 表达式语言 (EL)

Web Flow 使用 EL(表达式语言)来访问其数据模型并调用操作。 本章将帮助您熟悉 EL 语法、配置以及您可以从流程定义中引用的特殊 EL 变量。spring-doc.cadn.net.cn

EL 在流程中用于许多方面,包括:spring-doc.cadn.net.cn

EL 还用于将表单参数绑定到模型对象,反之亦然,用于根据模型对象的属性渲染格式化的表单字段。 然而,当使用 Web Flow 和 JSF 时,情况并非如此。 在这种情况下,适用标准的 JSF 组件生命周期。spring-doc.cadn.net.cn

12.1. 表达式类型

需要理解的一个重要概念是,Web Flow 中有两种类型的表达式:spring-doc.cadn.net.cn

12.1.1. 标准表达式

最常见的表达式类型是标准表达式。 此类表达式由EL直接计算,不需要用分隔符括起来,例如\#{}。 以下示例展示了这种标准表达式:spring-doc.cadn.net.cn

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

前述表达式是一个标准表达式,当被求值时,它会调用变量 searchCriteria 上的 nextPage 方法。 如果你尝试将此表达式包含在特殊分隔符中(例如 \#{}),则会得到一个 IllegalArgumentException。 在此上下文中,该分隔符被视为多余。 expression 属性唯一可接受的值是单个表达式字符串。spring-doc.cadn.net.cn

12.1.2. 模板表达式

第二种表达式是模板表达式。 模板表达式允许将文本字面量与一个或多个标准表达式混合使用。 每个标准表达式块都明确地被\#{}分隔符包围。 以下示例展示了一个模板表达式:spring-doc.cadn.net.cn

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

前面的表达式是一个模板表达式。 评估的结果是一个字符串,它将诸如 error-.xhtml 的文本与评估 externalContext.locale 的结果连接起来。 在这里,您需要明确的分隔符来划定模板中的标准表达式块。spring-doc.cadn.net.cn

请参阅 Web Flow XML 模式,以获取接受标准表达式和接受模板表达式的 XML 属性的完整列表。 您还可以在 Eclipse 中使用 F2(或在其他 IDE 中使用等效快捷键)来访问在键入特定流定义属性时的可用文档。

12.2. EL实现

Spring Web Flow 支持以下 EL(表达式语言)实现:spring-doc.cadn.net.cn

12.2.1. Spring 表达式语言 (Spring EL)

Web Flow 使用 Spring 表达式语言 (Spring EL)。Spring EL 的创建旨在为 Spring 产品组合中的所有产品提供一种统一且良好支持的表达式语言。 它作为单独的 jar 包(org.springframework.expression)分布在 Spring 框架中。spring-doc.cadn.net.cn

12.2.2. 统一表达式语言 (Unified EL)

使用 统一表达式语言(Unified EL) 还意味着对 el-api 的依赖,尽管这通常由您的 web 容器提供。 尽管 Spring EL 是默认且推荐使用的表达式语言,您可以将其替换为统一表达式语言(Unified EL)。 为此,您需要以下 Spring 配置,将 WebFlowELExpressionParser 插入到 flow-builder-services 中:spring-doc.cadn.net.cn

<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 配置了包含这些自定义转换器的转换服务,如下所示:spring-doc.cadn.net.cn

<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-doc.cadn.net.cn

然而,Spring El 有一些优势。 例如,Spring EL 与 Spring 3 的类型转换紧密集成,这使得您可以充分利用其特性。 具体来说,目前只有在使用 Spring EL 时才支持泛型类型的自动检测以及格式化注解的使用。spring-doc.cadn.net.cn

升级到 Spring EL 时,请注意以下与 Unified EL 相关的小改动:spring-doc.cadn.net.cn

  • 在流定义中用${}分隔的表达式必须更改为\#{}(注意开头的退格字符)。spring-doc.cadn.net.cn

  • Expressions that test the current event (such as #{currentEvent == 'submit'}) must be changed to \#{currentEvent.id == 'submit'} (note the addition of the id).spring-doc.cadn.net.cn

  • 解析属性(例如#{currentUser.name})可能会导致NullPointerException,而没有任何检查,例如\#{currentUser != null ? currentUser.name : null}。一个更好的替代方法是安全导航操作符:\#{currentUser?.name}spring-doc.cadn.net.cn

有关 Spring EL 语法的更多信息,请参阅 Spring 文档中的语言参考部分。spring-doc.cadn.net.cn

12.4. 特殊的EL变量

您可以在流程中引用多个隐式变量。spring-doc.cadn.net.cn

请记住以下通用规则: 只有当您将一个新变量赋值给某个作用域时,才应该使用引用数据作用域的变量(flowScopeviewScoperequestScope 等等)。spring-doc.cadn.net.cn

例如,当将调用bookingService.findHotels(searchCriteria)的结果赋值给一个名为hotels的新变量时,必须在其前面加上一个作用域变量,以让 Web Flow 知道你想将其存储在哪里。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<?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),你应该直接引用该变量,而无需在前面加上任何作用域变量,如下所示:spring-doc.cadn.net.cn

<?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>

以下是您可以在流定义中引用的隐式变量列表:spring-doc.cadn.net.cn

12.4.1. flowScope变量

你可以使用 flowScope 来分配一个流程变量。 流程范围在流程开始时分配,并在流程结束时销毁。 在默认实现中,存储在流程范围中的任何对象都需要可序列化。 以下代码定义了一个 flowScope 变量:spring-doc.cadn.net.cn

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

12.4.2. viewScope变量

你可以使用 viewScope 来分配一个视图变量。 视图作用域在进入 view-state 时被分配,并在状态退出时销毁。 视图作用域 能在 view-state 内部引用。 在默认实现中,存储在视图作用域中的任何对象都需要是可序列化的。 以下代码定义了一个 viewScope 变量:spring-doc.cadn.net.cn

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

12.4.3. requestScope变量

你可以使用 requestScope 来分配一个请求变量。 请求作用域在调用流时分配,并在流返回时销毁。 以下清单定义了一个 requestScope 变量:spring-doc.cadn.net.cn

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

12.4.4. flashScope变量

你可以使用 flashScope 来分配一个 flash 变量。 Flash 作用域在流程开始时分配,在每次视图渲染后清除,并在流程结束时销毁。 在默认实现中,存储在 flash 作用域中的任何对象都需要可序列化。 以下代码定义了一个 flashScope 变量:spring-doc.cadn.net.cn

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

12.4.5. conversationScope变量

您可以使用 conversationScope 来分配一个对话变量。 对话作用域在顶级流程开始时分配,并在顶级流程结束时销毁。 对话作用域由顶级流程及其所有子流程共享。 在默认实现中,对话作用域的对象存储在 HTTP 会话中,通常应可序列化以适应典型的会话复制。 以下清单定义了一个 conversationScope 变量:spring-doc.cadn.net.cn

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

12.4.6. requestParameters变量

requestParameters 变量访问客户端请求参数,如下所示:spring-doc.cadn.net.cn

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

12.4.7. currentEvent变量

变量 currentEvent 访问当前 Event 的属性,如下所示:spring-doc.cadn.net.cn

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

12.4.8. currentUser变量

变量 currentUser 访问已认证的 Principal,如下所示:spring-doc.cadn.net.cn

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

12.4.9. messageContext变量

The messageContext 变量访问上下文以检索和创建流执行消息,包括错误和成功消息。 有关更多信息,请参阅 MessageContext Javadocs。 以下示例使用了 messageContext 变量:spring-doc.cadn.net.cn

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

12.4.10. resourceBundle变量

变量 resourceBundle 访问消息资源,如下所示:spring-doc.cadn.net.cn

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

12.4.11. flowRequestContext变量

flowRequestContext 变量访问 RequestContext API,这是当前流请求的表示形式。 有关更多信息,请参阅 API Javadocsspring-doc.cadn.net.cn

12.4.12. flowExecutionContext变量

flowExecutionContext 变量访问 FlowExecutionContext API,这是当前流程状态的一个表示形式。 更多信息请参见 API Javadocsspring-doc.cadn.net.cn

12.4.13. flowExecutionUrl变量

flowExecutionUrl 变量访问当前流执行视图状态的上下文相关 URI。spring-doc.cadn.net.cn

12.4.14. externalContext变量

The externalContext 变量访问客户端环境,包括用户会话属性。 更多信息请参见 ExternalContext API JavaDocs。 以下示例使用了 externalContext 变量:spring-doc.cadn.net.cn

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

12.5. 作用域搜索算法

如本节前面所述,在将变量分配到某个流范围时,需要引用该范围。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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

当您只是访问某个作用域中的变量时,引用该作用域是可选的,如下所示:spring-doc.cadn.net.cn

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

当未指定作用域时,如同之前展示的booking的用法,将会使用一种作用域搜索算法。 该算法会在请求、闪存、视图、流程和会话作用域中查找该变量。 如果找不到这样的变量,则会抛出EvaluationExceptionspring-doc.cadn.net.cn