Spring实战第八章

Spring Web Flow

Spring Web Flow是一个Web框架,它适用于元素按规定流程运行的程序。

Spring Web Flow是Spring MVC的扩展,它支持开发基于流程的应用程序。它将流程的定义与实现流程行为的类和视图分离开来。

Spring Web Flow是构建于Spring MVC基础之上的。这意味着所有的流程请求都需要首先经过Spring MVC的DispatcherServlet。需要在Spring应用上下文中配置一些bean来处理流程请求并执行流程

只能在XML中对其进行配置。XML中配置的上下文定义XML文件中添加这个命名空间声明(位于与flows之中,位于spring应用上下文中):

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:flow="http://www.springframework.org/schema/webflow-config"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/webflow-config 
   http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 一、在Spring中配置Web Flow

1、装配流程执行器(位于flows中)

流程执行器(flow executor)驱动流程的执行。当用户进入一个流程时,流程执行器会为用户创建并启动一个流程执行实例。当流程暂停的时候(如为用户展示视图时),流程执行器会在用户执行操作后恢复流程

在Spring中,<flow:flow-executor>元素会创建一个流程执行器:

  <!-- Executes flows: the entry point into the Spring Web Flow system -->
  <flow:flow-executor id="flowExecutor" />

 

尽管流程执行器负责创建和执行流程,但它并不负责加载流程定义

2、配置流程注册表

流程注册表(flow registry)的工作是加载流程定义并让流程执行器能够使用它们。我们可以在Spring中使用<flow:flow-registry>配置流程注册表,如下所示:

  <!-- The registry of executable flow definitions -->
  <flow:flow-registry id="flowRegistry" 
           base-path="/WEB-INF/flows">
     <flow:flow-location-pattern value="/**/*-flow.xml" />
  </flow:flow-registry>

在这里的声明中,流程注册表会在“/WEB-INF/flows”目录下查找流程定义,这是通过base-path属性指明的。依据<flow:flow-location-pattern>元素的值,任何文件名以“-flow.xml”结尾的XML文件都将视为流程定义所有的流程都是通过其ID来进行引用的。这里我们使用了<flow:flow-location-pattern>元素,流程的ID就是相对于base-path的路径——或者双星号所代表的路径。图8.1展示了示例中的流程ID是如何计算的。????

 <flow:flow-location-pattern value="/*-flow.xml" />

 自动映射为下面的路径:

或者:作为另一种方式,我们可以去除base-path属性,而显式声明流程定义文件的位置:

 <flow:flow-registry id="flowRegistry">
     <flow:flow-location path="/WEB-INF/flows/springpizza.xml"/>
 </flow:flow-registry> 

 使用了<flow:flow-location>,path属性直接指明了“/WEB-INF/flows/springpizza.xml”作为流程定义。当我们这样配置的话,流程的ID是从流程定义文件的文件名中获得的,在这里就是springpizza。

再或者:

更显式地指定流程ID,那你可以通过<flow:flowlocation>元素的id属性来进行设置。

  <flow:flow-registry id="flowRegistry">
      <flow:flow-location id="pizza" path="/WEB-INF/flows/springpizza.xml"></flow:flow-location>
  </flow:flow-registry>

 3、处理流程请求

对于流程而言,我们需要一个FlowHandlerMapping来帮助DispatcherServlet将流程请求发送给Spring Web Flow。

  <!--Maps request paths to flows in the flowRegistry-->
  <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    <property name="flowRegistry" ref="flowRegistry" />
  </bean>

FlowHandlerMapping装配了流程注册表的引用,这样它就能知道如何将请求的URL匹配到流程上。例如,如果我们有一个ID为pizza的流程,FlowHandlerMapping就会知道如果请求的URL模式(相对于应用程序的上下文路径)是“/pizza”的话,就要将其匹配到这个流程上

然而,FlowHandlerMapping的工作仅仅是将流程请求定向到Spring Web Flow上,响应请求的是FlowHandlerAdapter。FlowHandlerAdapter等同于SpringMVC的控制器,它会响应发送的流程请求并对其进行处理。配置如下:

  <!--
   Dispatches requests mapped to flows to FlowHandler implementations
  -->
  <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <property name="flowExecutor" ref="flowExecutor" />
  </bean>

 

这个处理适配器是DispatcherServlet和Spring Web Flow之间的桥梁。它会处理流程请求并管理基于这些请求的流程。在这里,它装配了流程执行器的引用,而后者是为所处理的请求执行流程的。

 总的flows.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:flow="http://www.springframework.org/schema/webflow-config"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/webflow-config 
   http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="com.springinaction.pizza.flow" />

  <!-- Executes flows: the entry point into the Spring Web Flow system -->
  <flow:flow-executor id="flowExecutor" />

  <!-- The registry of executable flow definitions -->
  <flow:flow-registry id="flowRegistry" 
           base-path="/WEB-INF/flows">
     <flow:flow-location-pattern value="/**/*-flow.xml" />
  </flow:flow-registry>
  



  <!--Maps request paths to flows in the flowRegistry-->
  <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    <property name="flowRegistry" ref="flowRegistry" />
  </bean>

  <!--
   Dispatches requests mapped to flows to FlowHandler implementations
  -->
  <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <property name="flowExecutor" ref="flowExecutor" />
  </bean>

</beans>

 

 二、流程的组件

在Spring Web Flow中,流程是由三个主要元素定义的:状态、转移和流程数据状态(State)是流程中事件发生的地点。流程中的状态是业务逻辑执行、做出决策或将页面展现给用户的地方。如果流程状态就像公路旅行中停下来的地点,那转移(transition)就是连接这些点的公路。在流程中,你通过转移的方式从一个状态到另一个状态。在流程处理中,它要收集一些数据:流程的当前状况。

1、状态

Spring Web Flow定义了五种不同类型的状态

状 态 类 型 它是用来做什么的
行为(Action) 行为状态是流程逻辑发生的地方

决策(Decision)

决策状态将流程分成两个方向,它会基于流程数据的评估结果
确定流程方向

结束(End) 结束状态是流程的最后一站。一旦进入End状态,流程就会终止

子流程(Subflow)

子流程状态会在当前正在运行的流程上下文中启动一个新的流

视图(View) 视图状态会暂停流程并邀请用户参与流程

1.1视图状态

视图状态用于为用户展现信息并使用户在流程中发挥作用。实际的视图实现可以是Spring支持的任意视图类型,但通常是用JSP来实现的。

在流程定义的XML文件中,<view-state>用于定义视图状态

   <view-state id="welcome"/>

此时:id属性有两个含义。它在流程内标示这个状态。除此以外,因为在这里没有在其他地方指定视图,所以它也指定了流程到达这个状态时要展现的逻辑视图名为welcome

如果你愿意显式指定另外一个视图名,那可以使用view属性做到这一点:

    <view-state id="welcome" view="greeds"/>

如果流程为用户展现了一个表单,你可能希望指明表单所绑定的对象。为了做到这一点,可以设置model属性:

<view-state id="takePlayment" model="flowScope.paymentsDetails"/>    

指定takePayment视图中的表单将绑定流程作用域内的paymentDetails对象。

1.2、行为状态

行为状态则是应用程序自身在执行任务。行为状态一般会触发Spring所管理bean的一些方法根据方法调用的执行结果转移到另一个状态。在流程定义XML中,行为状态使用<action-state>元素来声明。

    <action-state id="saveOrder">
        <evaluate expression="pizzaFlowActions.saveOrder(order)" />
        <transition to="thankYou" />
    </action-state>

 

 <action-state>元素一般都会有一个<evaluate>作为子元素<evaluate>元素给出了行为状态要做的事情expression属性指定了进入这个状态时要评估的表达式。在本示例中,给出的expression是SpEL表达式,它表明将会找到ID为pizzaFlowActions的bean并调用其saveOrder()方法。

Spring Web Flow与表达式语言:SpEL是默认和推荐使用的表达式语言。

1.3决策状态

 决策状态能够在流程执行时产生两个分支。决策状态将评估一个Boolean类型的表达式,然后在两个状态转移中选择一个,这要取决于表达式会计算出true还是false。在XML流程定义中,决策状态通过<decision-state>元素进行定义。

    <decision-state id="checkDeliverArea">
        <if test="pizzaFlowActions.checkDeliverArea(customer.zipCode)"
            then="addCustomer"
            else ="deliveryWarning"/>
    </decision-state>

 

<decision-state>并不是独立完成工作的。<if>元素是决策状态的核心。这是表达式进行评估的地方,如果表达式结果为true,流程将转移到then属性指定的状态中,如果结果为false,流程将会转移到else属性指定的状态中。

1.4子流程状态

将流程分成独立的部分是个不错的主意。<subflow-state>允许在一个正在执行的流程中调用另一个流程。

    <!-- Order -->
    <subflow-state id="order" subflow="pizza/order">
      <input name="order" value="order"/>
      <transition on="orderCreated" to="payment" />
    </subflow-state>

 <input>元素用于传递订单对象作为子流程的输入。如果子流程结束的<end-state>状态ID为orderCreated,那么流程将会转移到名为payment的状态

sub

 1.5结束状态

所有的流程都要结束。这就是当流程转移到结束状态时所做的。<end-state>元素指定了流程的结束,它一般会是这样声明的:

 <end-state id="customerReady" />

 

当到达<end-state>状态,流程会结束。接下来会发生什么取决于几个因素:  

    如果结束的流程是一个子流程,那调用它的流程将会从<subflow-state>处继续执行。<end-state>的ID将会用作事件触发从<subflow-state>开始的转移。

    如果<end-state>设置了view属性,指定的视图将会被渲染。视图可以是相对于流程路径的视图模板,如果添加“externalRedirect:”前缀的话,将会重定向到流程外部的页面,如果添加“flowRedirect:”将重定向到另一个流程中。

    如果结束的流程不是子流程,也没有指定view属性,那这个流程只是会结束而已浏览器最后将会加载流程的基本URL地址,当前已没有活动的流程,所以会开始一个新的流程实例

需要意识到流程可能会有不止一个结束状态子流程的结束状态ID确定了激活的事件,所以你可能会希望通过多种结束状态来结束子流程,从而能够在调用流程中触发不同的事件。即使不是在子流程中,也有可能在结束流程后,根据流程的执行情况有多个显示页面供选择。

2转移

转移连接了流程中的状态。流程中除结束状态之外的每个状态,至少都需要一个转移,这样就能够知道一旦这个状态完成时流程要去向哪里。状态可以有多个转移,分别对应于当前状态结束时可以执行的不同的路径。

转移使用<transition>元素来进行定义,它会作为各种状态元素(<action-state>、<view-state>、<subflow-state>)的子元素。最简单的形式就是<transition>元素在流程中指定下一个状态:

        <transition to="customerReady" />

 

属性to用于指定流程的下一个状态。如果<transition>只使用了to属性,那这个转移就会是当前状态的默认转移选项,如果没有其他可用转移的话,就会使用它。

更常见的转移定义是基于事件的触发来进行的。在视图状态,事件通常会是用户采取的动作。在行为状态,事件是评估表达式得到的结果。而在子流程状态,事件取决于子流程结束状态的ID。在任意的事件中(这里没有任何歧义),你可以使用on属性来指定触发转移的事件:

        <transition on="phoneEntered" to="lookupCustomer"/>

如果触发了phoneEntered事件,流程将会进入lookupCustomer状态。

在抛出异常时,流程也可以进入另一个状态。例如,如果顾客的记录没有找到,你可能希望流程转移到一个展现注册表单的视图状态。以下的代码片段显示了这种类型的转移:

        <transition on-exception="com.springinacyion,pizza.service.CustomerNoFoundExcetion"
            to="registrationForm"/>

属性on-exception类似于on属性,只不过它指定了要发生转移的异常而不是一个事件。

2.1全局转移

可以将<transition>元素作为<global-transitions>的子元素,把它们定义为全局转移。

    <global-transitions>
        <transition on="cancel" to="endState"/>
    </global-transitions>

 定义完这个全局转移后,流程中的所有状态都会默认拥有这个cancel转移。

3、流程数据

当流程从一个状态进行到另一个状态时,它会带走一些数据。有时候,这些数据只需要很短的时间(可能只要展现页面给用户)。有时候,这些数据会在整个流程中传递并在流程结束的时候使用。

3.1声明变量

 流程数据保存在变量中,而变量可以在流程的各个地方进行引用。它能够以多种方式创建。在流程中创建变量的最简单形式是使用<var>元素:

<var name="customer" class="com.springinaction.pizza.domain.Customer"/>

 

 创建了一个新的Customer实例并将其放在名为customer的变量中。这个变量可以在流程的任意状态进行访问

作为行为状态的一部分或者作为视图状态的入口,可能会使用<evaluate>元素来创建变量。

         <evaluate result="viewScope.paymentTypeList" 
              expression="T(com.springinaction.pizza.domain.PaymentType).asList()" />

 

 在本例中,<evaluate>元素计算了一个表达式(SpEL表达式)并将结果放到了名为paymentTypeList的变量中,这个变量是视图作用域的。

<set>元素也可以设置变量的值:

          <set name="flowScope.paymentDetails" 
              value="new com.springinaction.pizza.domain.PaymentDetails()" />

 

 <set>元素与<evaluate>元素很类似,都是将变量设置为表达式计算的结果。

3.2定义流程数据的作用域

范  围 生命作用域和可见性
Conversation

最高层级的流程开始时创建,在最高层级的流程结束时销毁。被最
高层级的流程和其所有的子流程所共享

Flow

当流程开始时创建,在流程结束时销毁。只有在创建它的流程中是
可见的

Request 当一个请求进入流程时创建,在流程返回时销毁
Flash

当流程开始时创建,在流程结束时销毁。在视图状态渲染后,它也
会被清除

View

当进入视图状态时创建,当这个状态退出时销毁。只在视图状态内
是可见的

当使用<var>元素声明变量时,变量始终是流程作用域的,也就是在定义变量的流程内有效。当使用<set>或<evaluate>的时候,作用域通过name或result属性的前缀指定

 二、实例分析:

注意:首先对于订购披萨是一个整个流程,流程的ID是pizza,在注册流程的时候,<flow:flow-location-pattern value="/**/*-flow.xml" />,其中的**即为pizza(输入url:项目名/pizza)

其余的customer、order和payment是其子流程。

创建程序的目录:

pizza.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/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">   子流程状态。 调用customer子流程,完成对顾客订单的
      <output name="customer" value="order.customer"/>    使用customer子流程中customer类型填充order中的customer属性
      <transition on="customerReady" to="bulidOrder" />       当customer子流程的结束状态是customerReady时,转移到bulidOrder子流程
    </subflow-state>
    
    <!-- Order -->
    <subflow-state id="buildOrder" subflow="pizza/order">
      <input name="order" value="order"/>                           通过input使得主流程中的order来填充子流程中的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"> 简单视图,“/WEB-INF/flows/pizza/thankCustomer.jsp”
      <transition to="endState" />
    </view-state>
                
    <!-- End state -->
    <end-state id="endState" />
    
    <global-transitions>
      <transition on="cancel" to="endState" />
    </global-transitions>
</flow>

 其中对于:感谢界面

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<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>

 

 Spring Web Flow为视图的用户提供了一个flowExecutionUrl变量,它包含了流程的URL。结束链接将一个“_eventId”参数关联到URL上,以便回到Web流程时触发finished事件。这个事件将会让流程到达结束状态。

 

流程将会在结束状态完成。鉴于在流程结束后没有下一步做什么的具体信息,流程将会重新从identifyCustomer状态开始,以准备接受另一个披萨订单。

其他流程:

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

 

welcome状态是一个很简单的视图状态,它欢迎访问Spizza站点的顾客并要求他们输入电话号码。这个状态并没有什么特殊的。它有两个转移:如果从视图触发phoneEntered事件的话,转移会将流程定向到lookupCustomer,另外一个就是在全局转移中定义的用来响应cancel事件的cancel转移。

welcome视图:

<%@ 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)就是一种返回流程的“回程票”(claimticket)。当用户提交表单时,流程执行key会在“_flowExecutionKey”输入域中返回并在流程暂停的位置进行恢复

还要注意的是提交按钮的名字。按钮名字的“_eventId_”部分是提供给Spring Web Flow的一个线索,它表明了接下来要触发事件。当点击这个按钮提交表单时,会触发phoneEntered事件进而转移lookupCustomer。

 

 

 

 

 疑惑:1、Spring web flow的视图解析器在什么地方;2、为什么不能正常拦截

posted @ 2017-03-19 14:03  mslog  阅读(566)  评论(0编辑  收藏  举报