美文网首页
20、使用Spring Web Flow(2)(Spring笔记

20、使用Spring Web Flow(2)(Spring笔记

作者: yjaal | 来源:发表于2017-04-19 11:48 被阅读298次

    三、组合起来:披萨流程

    这里我们通过订购披萨的过程对流程进行说明。我们首先从构建一个高层次的流程开始,它定义了订购披萨的整体过程。接下来,我们会将这个流程拆分成子流程,这些子流程在较低层次定义了细节。

    3.1 定义基本流程

    一个新的披萨店决定允许用户在线订购以减轻店面电话的压力。当顾客访问Pizza站点时,他们需要进行用户识别,选择一个或更多披萨添加到订单中,提供支付信息然后提交订单并等待披萨送过来。如图所示。

    1

    下面给出实现披萨订单的整体流程:

    <?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">
    
        <var name="order" class="com.springinaction.pizza.domain.Order"/>
        
        <!-- Customer -->
        <subflow-state id="identifyCustomer" subflow="pizza/customer">
          <output name="customer" value="order.customer"/>
          <transition on="customerReady" to="buildOrder" />
        </subflow-state>
        
        <!-- Order -->
        <subflow-state id="buildOrder" subflow="pizza/order">
          <input name="order" value="order"/>
          <transition on="orderCreated" to="takePayment" />
        </subflow-state>
            
        <!-- Payment -->
        <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 -->
        <end-state id="endState" />
        
        <global-transitions>
          <transition on="cancel" to="endState" />
        </global-transitions>
    </flow>
    

    说明:在流程定义中,首先第一件事就是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;//pizza
       private Payment payment;//支付详情
    
       public Order() {
          pizzas = new ArrayList<Pizza>();
          customer = new Customer();
       }
    
    //getter 和setter方法这里省略
    }
    

    说明:默认情况下,流程定义文件中的第一个状态也会是流程访问中的第一个状态。本例中,也就是identifyCustomer状态(一个子流程)。也可以通过<flow>元素的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.0.xsd" 
      start-state="identifyCustomer">
    
    ......
    </flow>
    

    说明:

    • 识别顾客、构造披萨订单以及支付这样的活动太复杂了,并不适合将其强行塞入一个状态。后面将其单独定义为流程。但是为了更好地整体了解披萨流程,这些活动都是以<subflow-state>元素来进行展现的。

    • 流程变量order将在前三个状态中进行填充并在第四个状态中进行保存。identifyCustomer子流程状态使用<output>元素来填充ordercustomer属性,将其设置为顾客子流程收到的输出。buildOrdertakePayment状态使用了不同的方式,使用<input>order流程变量作为输入,这些子流程就能在其内部填充order对象。

    • 在订单得到顾客、一些披萨和支付细节后,saveOrder对其进行保存,是处理这个任务的行为状态。订单完成保存后,会转移到thankCustomer,这是一个简单的视图状态,后台使用"/WEB-INF/flows/flowspizza/thankCustomer.jsp",如下:

    <html>
    <head><title>Spring Pizza</title></head>
    <body>
        <h2>Thank you for your order!</h2>
        <![CDATA[
        <a href='${flowExecutionUrl}&_eventId=finished'>Finish</a>
        ]]>
    </body>
    </html>
    

    说明:在此页面中,会感谢顾客的订购并为其提供一个完成流程的链接。这个链接展示了用户与流程交互的唯一办法。此处提供了一个flowExecutionUrl变量,它包含了流程的URL。结束链接将一个"_eventId"参数关联到URL上,以便回到Web流程时触发finished事件。这个事件将会让流程到达结束状态。流程将会在结束状态完成。鉴于在流程结束后没有下一步做什么的具体信息,流程将会重新从identifyCustomer状态开始,以准备接受另一个披萨订单。

    3.2 收集顾客信息

    顾客在订购披萨的时候,我们需要知道用户的详细信息,特别是地址信息。这里可以根据电话号码进行查询(已注册过的用户),如果查询不到,则需要像用户询问。整个流程如下图所示:


    2

    下面对整个流程进行定义:

    <var name="customer" class="com.springinaction.pizza.domain.Customer" />
    
    <!-- Customer -->
    <view-state id="welcome"><!--欢迎顾客-->
        <transition on="phoneEntered" to="lookupCustomer"/>
        <transition on="cancel" to="cancel"/>
    </view-state>
    
    <action-state id="lookupCustomer"><!--查找顾客-->
        <evaluate result="customer" expression=
            "pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" /><!--将结果填充到customer中-->
        <transition to="registrationForm" on-exception=
            "com.springinaction.pizza.service.CustomerNotFoundException" /><!--发生异常后的转移-->
        <transition to="customerReady" />
    </action-state>
    
    <view-state id="registrationForm" model="customer"><!--注册新顾客-->
        <on-entry>
          <evaluate expression=
              "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">
        <ouput name="customer">
    </end-state>
    
    <global-transition>
        <transition on="cancel" to="cancel" />
    </global-transition>
    

    说明:这里书中有些地方可能是有点问题的。此流程应该说是整个订购流程的第一个分支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" %>
    <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"输入域。当进入视图状态时,流程暂停并等待用户采取一些行为。赋予视图的流程执行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" %>
    <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>
    

    3.2.4 检查配送区域

    在配送表单中需要告知顾客不能将披萨送到它们的地址(就是警告表单deliveryWarning.jsp):

    <%@ 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变量,这些链接分别触发流程中的acceptcancel事件。

    3.2.5 存储顾客数据

    这里使用addCustomer()方法对用户地址等数据进行保存,并将customer流程参数传递进去。

    3.2.6 结束流程

    这里给出了两个流程结束状态,而且使用<output>进行输出,这和整个披萨订购流程是相符合的。

    3.3 构建订单

    在识别完顾客之后,主流程的下一个事件就是确定它们想要干什么类型的披萨。订单子流程就是用于提示用户创建披萨并将其放入订单中的,如图所示。


    3

    整个流程较为简单,下面看如何定义此流程:

    <input name="order" required="true" />
    
    <!-- Order -->
    <view-state id="showOrder"><!-- 展现order的状态 -->
        <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" />
    

    说明:首先是输入一个order,这是主流程上创建的Order对象,这里使用<input>将主流程的Order对象进行了输入。createPizza状态的视图是一个表单,这个表单可以添加新的Pizza对象到订单中。<on-entry>元素添加了一个新的Pizza对象到流程作用域内,当表单提交时,表单的内容会填充到该对象中。需要注意的是,这个视图状态引用的model是流程作用域内的同一个Pizza对象(都是flowScope.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/>  
            <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>
    

    说明:这里对于showOrder.jsp就不给出了。当通过Continue按钮提交时,相关数据会绑定到Pizza对象中且触发addPizza转移。

    3.4 支付

    支付流程较为简单,首先是输入相关的支付信息,然后提交。具体流程如图所示。


    4

    下面给出流程定义:

    <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" />
    

    说明:对于选择支付信息的页面这里就不给出了。这里在进入视图时,<on-entry>元素构建了一个支付表单并创建了一个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<PaymentType> asList() {
        PaymentType[] all = PaymentType.values();
        return Arrays.asList(all);
      }
      
      @Override
      public String toString() {
        return WordUtils.capitalizeFully(name().replace('_', ' '));
      }
    }
    

    说明:至此,整个流程就执行完了。

    相关文章

      网友评论

          本文标题:20、使用Spring Web Flow(2)(Spring笔记

          本文链接:https://www.haomeiwen.com/subject/pvuczttx.html