Spring实战4之Spring Web Flow篇
来源:互联网 发布:apache显示目录列表 编辑:程序博客网 时间:2024/06/05 10:41
一、披萨流程
首先从构建一个高层次的流程开始,它定义了订购披萨的整体流程,然后将其拆分为多个子流程。
1.定义基本流程
当顾客访问Spizza网站时,他们需要进行用户识别、选择一个或多个披萨添加到订单、提供支付信息,然后提交订单,等待披萨上来,如下图:
网上购买披萨的流程
下面展示Spring Web Flow的XML流程定义来实现披萨订单的整体流程:
<?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/webflowhttp://www.springframework.org/schema/webflow/spring-webflow-2.3.xsd"> <var name="order" class="com.springinaction.pizza.domain.Order" /> <!-- 调用顾客子流程 --> <subflow-state id="identifyCustomer" subflow="pizza/customer"> <output name="customer" value="order.customer" /> <transition on="customerReady" to="buildOrder" /> </subflow-state> <!-- 调用订单子流程 --> <subflow-state id="buildOrder" subflow="pizza/order"> <input name="order" value="order" /> <transition on="orderCreated" to="takePayment" /> </subflow-state> <!-- 调用支付子流程 --> <subflow-state id="takePayment" subflow="pizza/payment"> <input name="order" value="order" /> <transition on="paymentTaken" to="saveOrder" /> </subflow-state> <!-- 保存订单 --> <action-state id="saveOrder"> <evaluate expression="pizzaFlowActions.saveOrder(order)" /> <transition to="thankCustomer" /> </action-state> <!-- 感谢顾客 --> <view-state id="thankCustomer"> <transition to="endState" /> </view-state> <end-state id="endState" /> <!-- 全局取消转移 --> <global-transitions> <transition on="cancel" to="endState" /> </global-transitions></flow>
流程定义中的第一件事就是声明order变量。每次流程开始的时候都会创建一个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<Pizza> pizzas; private Payment payment; public Order() { pizzas = new ArrayList<Pizza>(); customer = new Customer(); } //getters and setters}
流程定义的主要组成部分是流程的状态,默认情况下,流程定义文件中的第一个状态会是流程访问的第一个状态。本例中就是identifyCustomer状态(一个子流程)。也可以通过元素的start-state属性来指定任意状态为开始状态:<?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-2.3.xsd" start-state="identifyCustomer"> ...</flow>
识别顾客、构建披萨订单和支付这样的活动比较复杂,并不适合将其直接放在一个状态,而是以元素展现的。流程变量order将在前3个状态中进行填充并在第4个状态中进行保存。identifyCustomer子流程使用了元素来填充order的customer属性,将其设置为调用顾客子流程收到的输出。buildOrder和takePayment状态使用了不同的方式,它们使用将order流程变量作为输入,这些子流程就能在其内部填充order对象。在订单得到顾客、披萨以及支付信息后,就可以对其进行保存。saveOrder是处理这个任务的行为状态。它使用来调用ID为pizzaFlowActions的Bean的saveOrder()方法,并将保存的订单对象传递进来。订单完成保存后会转移到thankCustomer。thankCustomer状态是一个简单的视图状态,后台使用了/WEB-INF/flows/pizza/thankCustomer.jsp文件进行展示:<html xmlns:jsp="http://java.sun.com/JSP/Page"> <jsp:output omit-xml-declaration="yes" /> <jsp:directive.page contentType="text/html;charset=UTF-8" /> <head><title>Spizza</title></head> <body> <h2>Thank you for your order!</h2> <![CDATA[ <a href='${flowExecutionUrl}&_eventId=finished'>Finish</a> ]]> </body></html>
该页面提供了一个完成流程的链接,它展示了用户与流程交互的唯一办法。
Spring Web Flow为视图的用户提供了一个flowExecutionUrl变量,它包含了流程的URL。结束链接将一个_eventId参数关联到URL上,以便返回到Web流程时触发finished事件。这个事件将会使流程到达结束状态。
流程将会在结束状态完成。由于在流程结束后没有下一步做什么具体信息,流程将会重新从identifyCustomer状态开始,以准备接受下一个订单。
下面还要定义identifyCustomer、buildOrder、takePayment这些子流程。
2.收集顾客信息
对于一个顾客,需要收集其电话、住址等信息,如下面的流程图:
识别顾客流程
这个流程不再是线性的,而是有了分支。例如在查找顾客后,流程可能结束,也可能转到注册表单。同样的,在checkDeliveryArea状态,顾客可能会被告警,也可能是不被告警。
程序清单:
<?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-2.0.xsd"> <input name="order" required="true" /> <!-- Customer --> <view-state id="welcome"> <transition on="phoneEntered" to="lookupCustomer" /> <transition on="cancel" to="cancel" /> </view-state> <action-state id="lookupCustomer"> <evaluate result="order.customer" expression="pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" /> <transition to="registrationForm" on-exception="com.springinaction.pizza.service.CustomerNotFoundException" /> <transition to="customerReady" /> </action-state> <view-state id="registrationForm" model="order" popup="true"> <on-entry> <evaluate expression="order.customer.phoneNumber = requestParameters.phoneNumber" /> </on-entry> <transition on="submit" to="checkDeliveryArea" /> <transition on="cancel" to="cancel" /> </view-state> <decision-state id="checkDeliveryArea"> <if test="pizzaFlowActions.checkDeliveryArea(order.customer.zipCode)" then="addCustomer" else="deliveryWarning" /> </decision-state> <view-state id="deliveryWarning"> <transition on="accept" to="addCustomer" /> <transition on="cancel" to="cancel" /> </view-state> <action-state id="addCustomer"> <evaluate expression="pizzaFlowActions.addCustomer(order.customer)" /> <transition to="customerReady" /> </action-state> <!-- End state --> <end-state id="cancel" /> <end-state id="customerReady" /></flow>
下面将这个流程定义分解成一个个的状态。
(1)询问电话号码
welcome状态是一个很简单的视图状态,它欢迎访问Spizza网站的顾客并要求输入电话。它有两个转移:如果从视图触发phoneEntered事件,就会定向到lookupCustomer,另外一个就是在全局转移中定义用来响应cancel事件的cancel转移。
页面代码:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%><html><head><title>Spring Pizza</title></head><body> <h2>Welcome to Spring Pizza!!!</h2> <form:form> <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}" /> <input type="text" name="phoneNumber" /> <br /> <input type="submit" name="_eventId_phoneEntered" value="Lookup Customer" /> </form:form></body></html>
这个简单的表单用来让用户输入电话号码,有两个特殊的部分,首先是隐藏的_flowExecutionKey输入。当进入视图状态时,流程暂停并等待用户采取一些行为。当用户提交表单时,流程执行键会在_flowExecutionKey输入域中返回,并在流程暂停的位置进行恢复。
还需要注意提交按钮的名称eventId部分是Spring Web Flow的一个线索,它表明了接下来要触发事件。当点击这个按钮提交表单时,就会触发phoneEntered事件,进而转移到lookupCustomer。
(2)查找顾客
当欢迎顾客的表单提交后,顾客的电话号码将包含在请求参数中,并用于查询顾客。lookupCustomer状态的元素是查找发生的位置。它将电话号码从请求参数中抽取出来,并传递到pizzaFlowActions Bean的lookupCustomer()方法中。该方法要么返回Customer对象,要么抛出CustomerNotFoundException异常。
在前一种情况下,Customer对象会被设置到customer变量中(通过result属性)并默认的转移将流程带到customerReady状态。如果没有查到顾客,那么会抛出异常,流程会转移到registrationForm状态。
注册新顾客
registrationForm要求用户填写配送地址:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %><html> <head><title>Spring Pizza</title></head> <body> <h2>Customer Registration</h2> <form:form commandName="order"> <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/> <b>Phone number: </b><form:input path="customer.phoneNumber"/><br/> <b>Name: </b><form:input path="customer.name"/><br/> <b>Address: </b><form:input path="customer.address"/><br/> <b>City: </b><form:input path="customer.city"/><br/> <b>State: </b><form:input path="customer.state"/><br/> <b>Zip Code: </b><form:input path="customer.zipCode"/><br/> <input type="submit" name="_eventId_submit" value="Submit" /> <input type="submit" name="_eventId_cancel" value="Cancel" /> </form:form> </body></html>
该表单绑定到了Order.customer对象上。
(3)检查配送区域
顾客提供了地址后,需要确认住址是否在配送范围内,因此使用了决策状态。
决策状态checkDeliveryArea有一个元素,它将顾客的邮编传递到pizzaFlowActions Bean的checkDeliveryArea()方法中,该方法会返回一个Boolean值。
如果顾客在配送范围内,那么流程将转移到addCustomer状态,否则进入deliveryWarning视图状态。deliveryWarnin视图:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><html> <head><title>Spring Pizza</title></head> <body> <h2>Delivery Unavailable</h2> <p>The address is outside of our delivery area. The order may still be taken for carry-out.</p> <a href="${flowExecutionUrl}&_eventId=accept">Accept</a> | <a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a> </body></html>
其中有两个链接,允许用户继续订单或者取消订单。通过使用与welcome状态相同的flowExecutionUrl变量,这些链接分别触发流程中的accept和cancel事件。如果发送的是accept事件,那么流程会转移到addCustomer状态。否则,子流程会转移到cancel状态。(4)存储顾客数据addCustomer有一个元素,它会调用pizzaFlowActions.addCustomer()方法,将order.customer流程参数传递进去。一旦这个流程完成,就会执行默认转移,流程会转移到ID为customerReady的结束状态。结束流程当customer流程完成所有的路径后,会到达customerReady的结束状态。当调用它的披萨流程恢复时,它会接收到一个customerReady事件,这个事件将使得流程转移到buildOrder状态。注意,customerReady结束状态包含了一个元素。在流程中,它等同于Java的return语句。它会从子流程中传递一些数据到调用流程。例如,元素返回customer变量,这样披萨流程中的identifyCustomer子流程状态就可以将其指定给订单。另外,如果用户在任意地方触发了cancel事件,将会通过cancel状态结束流程,这也会在披萨流程中触发cancel事件并导致转移到披萨流程的结束状态。3.构建订单下面就是确定顾客想要什么样的披萨,提示用户创建披萨并将其放入订单,如图:通过订单子流程添加披萨可以看到,showOrder状态位于订单子流程的中心位置。这是用户进入这个流程时的状态,也是用户添加披萨订单后转移的目标状态。它展现了订单的当前状态,并允许用户添加其他的披萨到订单中。添加披萨订单时,会转移到createPizza状态。这是一个视图状态,允许用户对披萨进行选择。在showOrder状态,用户可以提交订单,也可以取消。<?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-2.0.xsd"> <input name="order" required="true" /> <!-- Order --> <view-state id="showOrder"> <transition on="createPizza" to="createPizza" /> <transition on="checkout" to="orderCreated" /> <transition on="cancel" to="cancel" /> </view-state> <view-state id="createPizza" model="flowScope.pizza"> <on-entry> <set name="flowScope.pizza" value="new com.springinaction.pizza.domain.Pizza()" /> <evaluate result="viewScope.toppingsList" expression="T(com.springinaction.pizza.domain.Topping).asList()" /> </on-entry> <transition on="addPizza" to="showOrder"> <evaluate expression="order.addPizza(flowScope.pizza)" /> </transition> <transition on="cancel" to="showOrder" /> </view-state> <!-- End state --> <end-state id="cancel" /> <end-state id="orderCreated" /></flow>
这个子流程实际上回操作主流程创建的Order对象,在这里我们使用元素来将Order对象传递进流程。
接下来会看到showOrder状态,它是一个基本的视图状态,具有3个不同的转移,分别用于创建披萨、提交订单和取消订单。
createPizza的视图是一个表单,这个表单可以添加新的Pizza对象到订单。元素添加了一个新的Pizza对象到流程作用域内,当表单提交时它将填充进订单。值得注意的是,这个视图状态引用的model是流程作用域同一个Pizza对象。Pizza对象将绑定到创建披萨的表单中:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %><div> <h2>Create Pizza</h2> <form:form commandName="pizza"> <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/> <b>Size: </b><br/> <form:radiobutton path="size" label="Small (12-inch)" value="SMALL"/><br/> <form:radiobutton path="size" label="Medium (14-inch)" value="MEDIUM"/><br/> <form:radiobutton path="size" label="Large (16-inch)" value="LARGE"/><br/> <form:radiobutton path="size" label="Ginormous (20-inch)" value="GINORMOUS"/><br/> <br/> <b>Toppings: </b><br/> <form:checkboxes path="toppings" items="${toppingsList}" delimiter="<br/>"/><br/><br/> <input type="submit" class="button" name="_eventId_addPizza" value="Continue"/> <input type="submit" class="button" name="_eventId_cancel" value="Cancel"/> </form:form></div>
当通过Continue按钮提交订单时,尺寸和配料选择会绑定到Pizza对象中,并且触发addPizza转移。与这个转移关联的元素表明在转移到showOrder状态之前,流程作用域内的Pizza对象会传递给订单的addPizza()方法中。
有两种方法可以结束流程,用户可以点击showOrder视图中的Cancel按钮或者Checkout按钮。这两种操作都会使流程转移到一个。但是选择的结束状态ID决定了退出这个流程时触发事件,进而最终确定主流程的下一个行为。主流程要么基于cancel要么基于orderCreated事件进行状态转移。在前者情况下,外边的流程会结束;后者,会转移到takePayment子流程。
4.支付
在披萨流程要结束的时候,最后的子流程提示用户输入他们的支付信息,如下图:
支付子流程
支付子流程也是使用元素接收一个Order对象作为输入。
可以看到,进入支付子流程的时候,用户会到达takePayment状态。这是一个视图状态,在这里用户可以选择信用卡、支票或者现金进行支付。提示支付信息后,进入verifyPayment状态,这是一个行为状态,会校验支付信息是否可以接受。
<?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-2.0.xsd"> <input name="order" required="true"/> <view-state id="takePayment" model="flowScope.paymentDetails"> <on-entry> <set name="flowScope.paymentDetails" value="new com.springinaction.pizza.domain.PaymentDetails()" /> <evaluate result="viewScope.paymentTypeList" expression="T(com.springinaction.pizza.domain.PaymentType).asList()" /> </on-entry> <transition on="paymentSubmitted" to="verifyPayment" /> <transition on="cancel" to="cancel" /> </view-state> <action-state id="verifyPayment"> <evaluate result="order.payment" expression= "pizzaFlowActions.verifyPayment(flowScope.paymentDetails)" /> <transition to="paymentTaken" /> </action-state> <!-- End state --> <end-state id="cancel" /> <end-state id="paymentTaken" /></flow>
在流程进入takePayment视图时,元素将构建一个支付表单并使用SpEL表达式在流程范围内创建PaymentDetails实例,该实例实际上是表单背后的对象。它也会创建视图作用域的paymentDetails变量,这个变量是一个包含了PaymentType enum的值的列表。在这里,SpEL的T()作用于PaymentType类,这样就可以调用静态的asList()方法。
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<PaymentType> asList() { PaymentType[] all = PaymentType.values(); return Arrays.asList(all); } @Override public String toString() { return WordUtils.capitalizeFully(name().replace('_', ' ')); }}
在面对支付表单的时候,用户可能提交支付,也可能会取消。根据做出的选择,支付子流程将名为paymentTaken或cancel的结束。就像其他的子流程一样,不论哪种都会结束子流程并将控制交给主流程。但是所采用的id将决定主流程接下来的转移。
目前我们已经依次介绍了披萨流程及其子流程,下面快速了解下如何对流程及其状态的访问增加安全保护。
保护Web流程
Spring Web Flow中的状态、转移甚至整个流程都可以借助元素实现安全性,该元素会作为这些元素的子元素。例如,为了保护对一个视图状态的访问:
<view-state id="restricted"> <secured attributes="ROLE_ADMIN" match="all"/></view-state>
按照这里的配置,只有授权ROLE_ADMIN访问权限(借助attributes属性)的用户才能访问这个视图状态。attributes属性使用逗号分隔的权限列表来表明用户要访问指定状态、转移或流程所需要的权限。match属性可以设置为any或all。如果是any,那么用户至上具备一个attributes属性所列的权限。如果的all,那么用户必须具有所有权限。
- Spring实战4之Spring Web Flow篇
- Spring学习笔记之Spring Web Flow
- 《Spring实战》学习笔记-第八章:使用Spring Web Flow
- 《Spring实战》学习笔记-第八章:使用Spring Web Flow
- Spring Web Flow
- Spring Web Flow
- spring web flow
- Spring web flow 配置文件
- spring web flow 随记
- Spring学习 WEB FLOW
- Spring Web Flow
- Spring Web Flow实例教程
- spring web flow起步
- Spring Web Flow
- Spring Web Flow
- 《Spring Web Flow 实践》
- Spring Web Flow 简介
- Spring Web Flow 1.0 Introduction
- 关于struts2 session传值
- to_char、to_date效率问题记录
- HAWQ取代传统数仓实践(五)——自动调度工作流(Oozie、Falcon)
- 如何使得textarea中的字体,显示换行效果等
- spring cloud-整合Swagger2构建RESTful服务的APIs
- Spring实战4之Spring Web Flow篇
- 安卓4种启动方式以及特点
- MySQL(用户和权限管理)
- 归并排序
- 换零钱
- 【STM32】STM32之timer3产生PWM
- Linux 热插拔事件
- java数据库存储图片
- 插入排序