官网资料
Spring Web Flow overview
Spring Web Flow API Doc.
Spring Web Flow Reference Guide
1. 介绍
1.1 环境需求
- Java 1.8 及以上
- Spring 5.0 及以上
1.2 基于Maven的Jar包引入
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
如果使用的是Java Server Faces,则引入以下依赖(包含了:spring-binding & spring-webflow)
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-faces</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
2. 定义流
2.1 什么是流
流:就是将可用于不同情境下的可重复使用的步骤提取出来。
例如:酒店订饭的流程
2.2 一个流是怎么组装的
在Spring Web Flow中,一个流是由一个个“状态”组装而成的一系列步骤。进入都每一个“状态”时,通常会有对应的视图展示给到用户。在视图中,用户的事件将交由“状态”做处理。这些事件可以触发到其他状态的转换,从而导致视图切换。
下面的例子展现了上面流程图中:book hotel流的结构
2.3 怎么创建流
流是由web应用程序开发人员使用简单的基于xml的流定义语言编写的
2.4 Essential language elements
2.4.1 flow
所有的flow都从这个根元素开始
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
</flow>
所有的状态都在这个元素内定义。第一个定义的状态将作为flow的起点。
2.4.2 view-state
使用view-state元素,用于定义一个渲染视图的步骤
<view-state id="enterBookingDetails" />
按照惯例,view-state将其id映射到flow所在目录中的视图模板。
例如:如果flow是位于:/WEB-INF/hotels/booking这个目录下的,那么上面的例子则对应的是 /WEB-INF/hotels/booking/enterBookingDetails.xhtml 这个视图。
2.4.3 transition
使用transition元素处理状态中发生的事件:
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
触发submit事件时,将跳转到reviewBooking页面
2.4.4 end-state
定义结束元素
<end-state id="bookingCancelled" />
当流转换到结束状态时,它将终止并返回结果。
2.4.5 综合例子
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" />
<end-state id="bookingCancelled" />
</flow>
2.5 Actions
大多数流需要表达的不仅仅是视图导航逻辑。通常,它们还需要调用应用程序的业务服务或其他操作。
在流中,您可以在几个点上执行操作:
- On flow start
- On state entry
- On view render
- On transition execution
- On state exit
- On flow end
2.5.1 evaluate
evaluate将会是你使用最多的元素。使用 evaluate元素在流中的某一点对表达式求值。通过这个标签,您可以调用Spring bean或任何其他流变量上的方法。例如:
<evaluate expression="entityManager.persist(booking)" />
数据赋值
如果表达式返回一个值,那么这个值可以保存在flowScope的数据模型上
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />
数据转换
如果表达式返回的值需要被转换,可以使用result-type这个元素
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" result-type="dataModel"/>
2.5.2 综合例子
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" />
<end-state id="bookingCancelled" />
</flow>
2.6 Input/Output Mapping
每个流都有一个定义良好的输入/输出约定。流开始时可以传递输入属性,结束时可以返回输出属性。在这方面,调用流在概念上类似于使用以下签名调用方法。
FlowOutcome flowId(Map<String, Object> inputAttributes);
public interface FlowOutcome {
public String getName();
public Map<String, Object> getOutputAttributes();
}
2.6.1 input
使用input元素来定义一个流中的输入值
<input name="hotelId" />
输入值保存在流作用域中属性的名称下。例如,上面的输入将保存在名称hotelId下。
定义输入值的类型
使用type属性来定义输入值的类型
<input name="hotelId" type="long" />
如果输入值与声明的类型不匹配,将尝试做类型转换。
输入值的分配
使用value属性指定要为其分配输入值的表达式
<input name="hotelId" value="flowScope.myParameterObject.hotelId" />
设定输入值为必填项
使用required属性,使得输入值不为空
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />
2.6.2 output
使用output元素来定义flow中的output属性。output属性在end-states标签中定义。
<end-state id="bookingConfirmed">
<output name="bookingId" />
</end-state>
指定输出值的源
使用value属性表示特定的输出值表达式
<output name="confirmationNumber" value="booking.confirmationNumber" />
2.6.3 综合例子
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" >
<output name="bookingId" value="booking.id"/>
</end-state>
<end-state id="bookingCancelled" />
</flow>
2.7 Variables
流可以声明一个或多个实例变量。这些变量在流开始时分配。当流恢复时,变量持有的任何@Autowired临时引用也会被重新连接
2.7.1 var
使用var元素声明一个流变量
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
确保variable对应的class实现了java.io.Serializable
2.8 Variable Scopes
Web Flow可以在几种作用域中存储变量
2.8.1 Flow Scope
Flow Scope在流开始时分配,在流结束时销毁。使用默认实现,Flow Scope中存储的任何对象都需要是可序列化的。
2.8.2 View Scope
View Scope作用于view-state的开始和结束期间。View Scope只能作用于view-state,且也是需要可序列化的。
2.8.3 Request Scope
Request Scope在调用流时分配,在流返回时销毁
2.8.4 Flash Scope
Flash Scope在流开始时分配,在每次视图渲染后清除,在流结束时销毁。在默认实现的情况下,任何存储在Flash Scope的对象都需要序列化。
2.8.5 Conversation Scope
Conversation scope gets allocated when a top-level flow starts and destroyed when the top-level flow ends. Conversation scope is shared by a top-level flow and all of its subflows. With the default implementation, conversation scoped objects are stored in the HTTP session and should generally be Serializable to account for typical session replication.
The scope to use is often determined contextually, for example depending on where a variable is defined – at the start of the flow definition (flow scope), inside a a view state (view scope), etc. In other cases, for example in EL expressions and Java code, it needs to be specified explicitly. Subsequent sections explain how this is done.
<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
2.9 Calling subflows
流可以调用另一个流作为子流(subflows)。流将等待直到子流返回,然后响应子流结果。
2.9.1 subflow-state
使用subflow-state去调用另一个流作为它的子流。
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
<transition on="creationCancelled" to="reviewBooking" />
</subflow-state>
上面的例子中,将会调用 createGuest 流并且等待它的结束。当createGuest流结束并返回时,新的顾客将会添加到顾客预订列表中。
Passing a subflow input
使用input标签,将值传入到子流中
<subflow-state id="addGuest" subflow="createGuest">
<input name="booking" />
<transition to="reviewBooking" />
</subflow-state>
Mapping subflow output
When a subflow completes, its end-state id is returned to the calling flow as the event to use to continue navigation.
The subflow can also create output attributes to which the calling flow can refer within an outcome transition as follows:
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
在上面的例子中,guest是guestCreated结果返回的输出属性的名称。
综合例子
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="addGuest" to="addGuest" />
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
<transition on="creationCancelled" to="reviewBooking" />
</subflow-state>
<end-state id="bookingConfirmed" >
<output name="bookingId" value="booking.id" />
</end-state>
<end-state id="bookingCancelled" />
</flow>
3. Expression Language (EL)
3.1 Introduction
EL用于流中的许多事情,包括:
- 访问客户端数据,例如声明流输入或引用请求参数
- 访问Web流的RequestContext中的数据,例如flowScope或currentEvent
- 通过操作调用spring管理对象上的方法
- 解析表达式,如状态转换标准、子流id和视图名称
3.1.1 Expression types
在Web Flow中,有两个非常重要的概念需要理解。分别是:standard expressions 和 template expressions
Standard Expressions
这些表达式直接由EL计算,不需要加**#{}**
<evaluate expression="searchCriteria.nextPage()" />
上面的表达式是一个标准表达式,在求值时调用searchCriteria变量上的nextPage方法。如果您试图用特殊的分隔符(如#{})括起这个表达式,将会得到一个IllegalArgumentException。在此上下文中,分隔符被视为多余。expression属性唯一可接受的值是单个表达式字符串。
Template expressions
第二种类型的表达式是模板表达式。模板表达式允许将文本与一个或多个标准表达式混合使用。每个标准表达式块都显式地由 #{} 分隔符包围。
<view-state id="error" view="error-#{externalContext.locale}.xhtml" />
上面的表达式是一个模板表达式。求值的结果将是一个字符串,将诸如error-和.xhtml等文本与externalContext.locale对应的值的结果连接起来。这里需要显式分隔符 #{} 来划分模板中的标准表达式。
4.2 EL Implementations
4.2.1 Spring EL
Web Flow使用Spring表达式语言(Spring EL)。Spring EL的创建是为了提供一种单一的、支持性好的表达式语言,用于Spring中的所有产品。它在Spring框架中作为一个单独的jar包 org.springframework发布。
Spring Expression Language (SpEL)
4.2.2 Unified EL
使用统一EL也意味着对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 portability
通常,您会发现Spring EL和Unified EL具有非常相似的语法
但是请注意,Spring EL有一些优点。例如,Spring EL与Spring 3的类型转换紧密集成,这允许您充分利用其特性。具体来说,目前只有Spring EL支持泛型类型的自动检测和格式化注释的使用。
当从统一EL升级到Spring EL时,需要注意一些小的变化,如下所示:
- ${} 要转换成 #{}
- #{currentEvent == ‘submit’} 转换成 #{currentEvent.id == ‘submit’}
- 解析 #{currentUser.name} 时如果没有做 #{currentUser != null ? currentUser.name : null} 的判空处理,则可能会抛NullPointerException。更好的做法是:#{currentUser?.name}
4.4 Special EL variables
请记住这条一般规则。引用数据作用域的变量(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>
但是,在设置/修改现有变量的值时,不需要加scope variables。
<?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
4.4.2 viewScope
4.4.3 requestScope
4.4.4 flashScope
4.4.5 conversationScope
4.4.6 requestParameters
使用requestParameters访问客户端请求参数
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
4.4.7 currentEvent
使用currentEvent访问当前事件的属性
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
4.4.8 currentUser
Use currentUser to access the authenticated Principal:
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
4.4.9 messageContext
使用messageContext访问上下文,以检索和创建流执行消息,包括错误和成功消息
<evaluate expression="bookingValidator.validate(booking, messageContext)" />
4.4.10 resourceBundle
使用resourceBundle访问消息资源。
<set name="flashScope.successMessage" value="resourceBundle.successMessage" />