三、组合起来:披萨流程
这里我们通过订购披萨的过程对流程进行说明。我们首先从构建一个高层次的流程开始,它定义了订购披萨的整体过程。接下来,我们会将这个流程拆分成子流程,这些子流程在较低层次定义了细节。
3.1 定义基本流程
一个新的披萨店决定允许用户在线订购以减轻店面电话的压力。当顾客访问Pizza
站点时,他们需要进行用户识别,选择一个或更多披萨添加到订单中,提供支付信息然后提交订单并等待披萨送过来。如图所示。
下面给出实现披萨订单的整体流程:
说明:在流程定义中,首先第一件事就是order
变量的声明。每次流程开始的时候,都会创建一个Order
实例。
package com.springinaction.pizza.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("order")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
private Customer customer;//顾客
private List pizzas;//pizza
private Payment payment;//支付详情
public Order() {
pizzas = new ArrayList();
customer = new Customer();
}
//getter 和setter方法这里省略
}
说明:默认情况下,流程定义文件中的第一个状态也会是流程访问中的第一个状态。本例中,也就是identifyCustomer
状态(一个子流程)。也可以通过
元素的start-state
属性显示的指定任意状态为开始状态:
......
说明:
识别顾客、构造披萨订单以及支付这样的活动太复杂了,并不适合将其强行塞入一个状态。后面将其单独定义为流程。但是为了更好地整体了解披萨流程,这些活动都是以
元素来进行展现的。流程变量
order
将在前三个状态中进行填充并在第四个状态中进行保存。identifyCustomer
子流程状态使用元素来填充
order
的customer
属性,将其设置为顾客子流程收到的输出。buildOrder
和takePayment
状态使用了不同的方式,使用将
order
流程变量作为输入,这些子流程就能在其内部填充order
对象。在订单得到顾客、一些披萨和支付细节后,
saveOrder
对其进行保存,是处理这个任务的行为状态。订单完成保存后,会转移到thankCustomer
,这是一个简单的视图状态,后台使用"/WEB-INF/flows/flowspizza/thankCustomer.jsp"
,如下:
Spring Pizza
Thank you for your order!
Finish
]]>
说明:在此页面中,会感谢顾客的订购并为其提供一个完成流程的链接。这个链接展示了用户与流程交互的唯一办法。此处提供了一个flowExecutionUrl
变量,它包含了流程的URL
。结束链接将一个"_eventId"
参数关联到URL
上,以便回到Web
流程时触发finished
事件。这个事件将会让流程到达结束状态。流程将会在结束状态完成。鉴于在流程结束后没有下一步做什么的具体信息,流程将会重新从identifyCustomer
状态开始,以准备接受另一个披萨订单。
3.2 收集顾客信息
顾客在订购披萨的时候,我们需要知道用户的详细信息,特别是地址信息。这里可以根据电话号码进行查询(已注册过的用户),如果查询不到,则需要像用户询问。整个流程如下图所示:
下面对整个流程进行定义:
说明:这里书中有些地方可能是有点问题的。此流程应该说是整个订购流程的第一个分支identifyCustomer
,所以,对比来看,这里首先是定义了一个customer
的变量,而最后将customer
进行了输入,这与大流程是相符的。
3.2.1 询问电话号码
下面给出欢迎视图:"/WEB-INF/flows/pizza/customer/welcome.jsp"
:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
Spring Pizza
Welcome to Spring Pizza!!!
说明:表单中有一个隐藏的"_flowExecutionKey"
输入域。当进入视图状态时,流程暂停并等待用户采取一些行为。赋予视图的流程执行key(flow execution key)
就是一种返回流程的“回程票”(claim ticket)
。当用户提交表单时,流程执行key
会在“_flowExecutionKey”
输入域中返回并在流程暂停的位置进行恢复。同时,按钮的名字“_eventId_”
部分是提供给Spring Web Flow
的一个线索,它标明了接下来要触发的事件。当点击这个按钮提交表单时,会触发phoneEntered
事件进而转移到lookupCustomer
。
3.2.2 查找顾客
目前,lookupCustomer()
方法的实现并不重要,这里只需要知道要么返回customer
对象,要么抛出异常。返回的结果会通过result
属性设置到输入变量customer
中。
3.2.3 注册新顾客
注册表单如下:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
Spring Pizza
Customer Registration
Phone number:
Name:
Address:
City:
State:
Zip Code:
3.2.4 检查配送区域
在配送表单中需要告知顾客不能将披萨送到它们的地址(就是警告表单deliveryWarning.jsp
):
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Spring Pizza
Delivery Unavailable
The address is outside of our delivery area. The order may still be taken for carry-out.
Accept |
Cancel
说明:这里提供了两个链接,允许用户继续订单或者取消。通过使用与welcome
状态相同的flowExecutionUrl
变量,这些链接分别触发流程中的accept
或cancel
事件。
3.2.5 存储顾客数据
这里使用addCustomer()
方法对用户地址等数据进行保存,并将customer
流程参数传递进去。
3.2.6 结束流程
这里给出了两个流程结束状态,而且使用
3.3 构建订单
在识别完顾客之后,主流程的下一个事件就是确定它们想要干什么类型的披萨。订单子流程就是用于提示用户创建披萨并将其放入订单中的,如图所示。
整个流程较为简单,下面看如何定义此流程:
说明:首先是输入一个order
,这是主流程上创建的Order
对象,这里使用将主流程的
Order
对象进行了输入。createPizza
状态的视图是一个表单,这个表单可以添加新的Pizza
对象到订单中。
元素添加了一个新的Pizza
对象到流程作用域内,当表单提交时,表单的内容会填充到该对象中。需要注意的是,这个视图状态引用的model
是流程作用域内的同一个Pizza
对象(都是flowScope.pizza
)。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
Create Pizza
Size:
Toppings:
说明:这里对于showOrder.jsp
就不给出了。当通过Continue
按钮提交时,相关数据会绑定到Pizza
对象中且触发addPizza
转移。
3.4 支付
支付流程较为简单,首先是输入相关的支付信息,然后提交。具体流程如图所示。
下面给出流程定义:
说明:对于选择支付信息的页面这里就不给出了。这里在进入视图时,
元素构建了一个支付表单并创建了一个PaymentDetails
实例。之后还创建了视图作用域的paymentTypeList
变量,这个变量是一个列表包含了PaymentType
枚举的值。这里需要调用一个adList()
方法。
package com.springinaction.pizza.domain;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.text.WordUtils;
public enum PaymentType {
CASH, CHECK, CREDIT_CARD;
public static List asList() {
PaymentType[] all = PaymentType.values();
return Arrays.asList(all);
}
@Override
public String toString() {
return WordUtils.capitalizeFully(name().replace('_', ' '));
}
}
说明:至此,整个流程就执行完了。