1. 程式人生 > >Spring Web Flow 入門demo(三)巢狀流程與業務結合 附原始碼

Spring Web Flow 入門demo(三)巢狀流程與業務結合 附原始碼

上篇部落格我們說Spring web Flow與業務結合的方式主要有三種,下面我們主要介紹一下第三種的應用方式

3,執行到<action-state> 元素

SpringWeb Flow 中的這個 <action-state> 是專為執行業務邏輯而設的 state 。如果某個應用的業務邏輯程式碼既不適合放在transition 中由客戶端來觸發,也不適合放在 Spring Web Flow 自定義的切入點,那麼就可以考慮新增<action-state> 元素專用於該業務邏輯的執行。更傾向於觸發某個事件來執行。

action-state 示例:

<action-state id="addToCart">
<evaluate expression="cart.addItem(productService.getProduct(productId))"/>
<transition to="productAdded"/>
</action-state>

新增subflow 結點

商品列表已經實現了,接下來操作步驟為:

  1. 實現 Cart 和 CartItem 兩個業務類
  2. 在 shopping.xml 中新增配置
  3. 在 /WEB-INF/flows 目錄下新增 addToCart.xml
  4. 在 webflow-config.xml 中新增 addToCart.xml 的位置
  5. 修改 viewCart.jsp 頁面

具體demo實現:

Cart

package samples.webflow;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//購物車的實現類
public class Cart implements Serializable {

    private static final long serialVersionUID = 7901330827203016310L;
    private Map<Integer, CartItem> map = new HashMap<Integer, CartItem>();
   
    //getItems 用於獲取當前購物車裡的物品
    public List<CartItem> getItems() {
	return new ArrayList<CartItem>(map.values());
    }
    
    //addItem 用於向購物車新增商品
    public void addItem(Product product) {
	int id = product.getId();
	CartItem item = map.get(id);
	if (item != null)
	    item.increaseQuantity();
	else
	    map.put(id, new CartItem(product, 1));
    }

    //getTotalPrice 用於獲取購物車裡所有商品的總價格
    public int getTotalPrice() {
	int total = 0;
	for (CartItem item : map.values())
	    total += item.getProduct().getPrice() * item.getQuantity();
	return total;
    }
}

Cart 是購物車的實現類,其同樣要實現java.io.Serializable 介面,但它沒有像 ProductService 一樣成為由 Spring IoC 容器管理的 Bean,每個客戶的購物車是不同的,因此不能使用 Spring IoC 容器預設的 Singleton 模式。

CartItem:

package samples.webflow;

import java.io.Serializable;

//購物車中的條目
public class CartItem implements Serializable {
    private static final long serialVersionUID = 8388627124326126637L;
    private Product product;//商品
    private int quantity;//數量

    public CartItem(Product product, int quantity) {
	this.product = product;
	this.quantity = quantity;
    }

    //計算該條目的總價格
    public int getTotalPrice() {
	return this.quantity * this.product.getPrice();
    }

    //增加商品的數量
    public void increaseQuantity() {
	this.quantity++;
    }

    /**
     * Return property product
     */
    public Product getProduct() {
	return product;
    }

    /**
     * Sets property product
     */
    public void setProduct(Product product) {
	this.product = product;
    }

    /**
     * Return property quantity
     */
    public int getQuantity() {
	return quantity;
    }

    /**
     * Sets property quantity
     */
    public void setQuantity(int quantity) {
	this.quantity = quantity;
    }

    /*   getter  setter */

}

shopping.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">

	<!-- 在 shopping flow 開始時必須分配一個 Cart 物件,由於要呼叫 subflow ,
	這個 Cart 物件應存放於 conversationScope 中。
	同時要新增一個 subflow-state 用於執行新增商品到購物車的任務。 -->
	<!-- mycart為一個服務類 -->
	<var name="mycart" class="samples.webflow.Cart" />
	<on-start>
		<set name="conversationScope.cart" value="mycart"></set>
	</on-start>
	<!-- view-state中的view對應jsp資料夾中的jsp頁面,on是觸發事件,to對應state id -->
	<view-state id="viewCart" view="viewCart">
		<!-- 在進入 view 的 render 流程之後,在 view 真正 render出來之前 -->
		<on-render>
			<!-- 要在 viewCart 頁面中顯示商品,只需在 view-state 元素的 on-render 切入點呼叫 productService 
				的 getProducts 方法,並將所得結果儲存到 viewScope 中即可 -->
			<evaluate expression="productService.getProducts()" result="viewScope.products" />
		</on-render>
		<transition on="submit" to="viewOrder" />
		<transition on="addToCart" to="addProductToCart" />
	</view-state>
	
	<subflow-state id="addProductToCart" subflow="addToCart">
		<transition on="productAdded" to="viewCart" />
	</subflow-state>
	
	<view-state id="viewOrder" view="viewOrder">
		<transition on="confirm" to="orderConfirmed">
		</transition>
	</view-state>
	<view-state id="orderConfirmed" view="orderConfirmed">
		<transition on="returnToIndex" to="returnToIndex">
		</transition>
	</view-state>
	
	<end-state id="returnToIndex" view="externalRedirect:servletRelative:/index.jsp">
	</end-state>
</flow>

在/WEB-INF/flows 目錄下新增 addToCart.xml

subflow-state元素的 subflow 屬性即指明瞭這個被呼叫的 flow 的 id 為“ addToCart ”,現在就要新增addToCart flow的定義。

addToCart.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">
    
	<!-- flow 執行之前 ,productId這個欄位內容從viewCart頁面中獲取-->
	<on-start>
		<set name="requestScope.productId" value="requestParameters.productId" />
	</on-start>
	
	<!-- addToCart flow 主要由一個 action-state 構成,完成新增商品到購物車的功能,
	 addToCart flow 的實現需要有輸入引數,即 productId 。
	 本示例中是通過請求引數來傳遞,通過 requestParameters 來獲取該數值。
	 這裡還要注意到 end-state 的 id 為“ productAdded ”,
	 與 subflow-state 中的 transition元素的on屬性的名稱是對應的。 -->
	
	<action-state id="addToCart">
		<evaluate expression="cart.addItem(productService.getProduct(productId))" />
		<transition to="productAdded" />
	</action-state>
	<end-state id="productAdded" />
</flow>

webflow-config.xml 中新增addToCart.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
	http://www.springframework.org/schema/context 
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<!-- 搜尋 samples.webflow 包裡的 @Component 註解,並將其部署到容器中 -->
	<context:component-scan base-package="samples.webflow" />
	<!-- 啟用基於註解的配置 -->
	<context:annotation-config />
	<import resource="webmvc-config.xml" />
	<import resource="webflow-config.xml" />
</beans>

viewCart.jsp:

<?xml version="1.0" encoding="utf-8" ?>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>View Cart</title>
</head>
<body>
	<h1>View Cart</h1>
	<h2>Items in Your Cart</h2>
	<c:choose>
		<c:when test="${empty cart.items}">
			<p>Your cart is empty.</p>
		</c:when>
		<c:otherwise>
			<table border="1" cellspacing="0">
				<tr>
					<th>Item</th>
					<th>Quantity</th>
					<th>Unit Price</th>
					<th>Total</th>
				</tr>

				<c:forEach var="item" items="${cart.items}">
					<tr>
						<td>${item.product.description}</td>
						<td>${item.quantity}</td>
						<td>${item.product.price}</td>
						<td>${item.totalPrice}</td>
					</tr>
				</c:forEach>

				<tr>
					<td>TOTAL:</td>
					<td></td>
					<td></td>
					<td>${cart.totalPrice}</td>
				</tr>
			</table>
		</c:otherwise>
	</c:choose>

	<a href="${flowExecutionUrl}&_eventId=submit">Submit</a>
	<h2>Products for Your Choice</h2>

	<table>
		<c:forEach var="product" items="${products}">
			<tr>
				<td>${product.description}</td>
				<td>${product.price}</td>


				<td><a
					href="${flowExecutionUrl}&_eventId=addToCart&productId=${product.id}">[add
						to cart]</a></td>


			</tr>
		</c:forEach>

	</table>
</body>
</html>

viewOrder.jsp:

<?xml version="1.0" encoding="utf-8" ?>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>view order</title>
</head>
<body>
	<h1>Order</h1>
	<c:choose>
		<c:when test="${empty cart.items}">
			<p>Your cart is empty.</p>
		</c:when>
		<c:otherwise>
			<table border="1" cellspacing="0">
				<tr>
					<th>Item</th>
					<th>Quantity</th>
					<th>Unit Price</th>
					<th>Total</th>
				</tr>

				<c:forEach var="item" items="${cart.items}">
					<tr>
						<td>${item.product.description}</td>
						<td>${item.quantity}</td>
						<td>${item.product.price}</td>
						<td>${item.totalPrice}</td>
					</tr>
				</c:forEach>

				<tr>
					<td>TOTAL:</td>
					<td></td>
					<td></td>
					<td>${cart.totalPrice}</td>
				</tr>
			</table>
		</c:otherwise>
	</c:choose>
	
	<a href="${flowExecutionUrl}&_eventId=confirm">Confirm</a>
</body>
</html>


訪問地址:

顯示效果:



再擴充套件一下:

如果我們將shopping.xml中的配置檔案修改一下,改為flowScope時,我們在viewOrder頁面也可以獲取products資料。

<view-state id="viewCart" view="viewCart">
		<!-- 在進入 view 的 render 流程之後,在 view 真正 render出來之前 -->
		<on-render>
			<!-- 要在 viewCart 頁面中顯示商品,只需在 view-state 元素的 on-render 切入點呼叫 productService 
				的 getProducts 方法,並將所得結果儲存到 viewScope 中即可 -->
			<evaluate expression="productService.getProducts()" result="flowScope.products" />
		</on-render>
		<transition on="submit" to="viewOrder" />
		<transition on="addToCart" to="addProductToCart" />
	</view-state>

viewOrder.jsp:
<h2>Products for Your Choice</h2>

	<table>
	<c:forEach var="product" items="${products}">
		<tr>
			<td>${product.description}</td>
			<td>${product.price}</td>
		</tr>			
	</c:forEach>
	</table>
	<a href="${flowExecutionUrl}&_eventId=confirm">Confirm</a>

效果圖:


總結:

    Spring Web Flow 應用流程的方式解決了資料存取範圍的問題,並在解決資料存取範圍問題的同時,通過使用xml的方式來控制頁面間的流轉順序以及頁面間資料的傳輸,使得我們頁面間的跳轉變得更加靈活可控。

附原始碼