到現在為止,這個購物車應用只是實現了頁面之間的跳轉,接下來我們要實現與業務邏輯相關的功能。由於本教程的重點在於介紹如何應用 Spring Web Flow ,所實現的業務比較簡單,與實際應用有較大的距離,請讀者諒解。
業務的邏輯涉及到數據的獲取、傳遞、保存,相關的業務功能函數的調用等內容,這些功能的實現都可用 Java 代碼來完成,但定義 Spring Web Flow 的語法與 Java 是無關的,這就要求 Spring Web Flow 提供與 Java 代碼的整合機制。要了解這種機制,關鍵在於搞清楚兩個問題:
業務邏輯代碼在什麼時候被調用?
業務邏輯代碼在調用後得到的數據如何保存、傳遞?
業務邏輯代碼在什麼時候被調用?
在 Spring Web Flow 中,業務邏輯代碼的執行可由以下三種情形來觸發:
客戶端請求中包含了 _eventId 參數
執行到框架自定義的切入點
執行到 <action-state> 元素
客戶端請求中包含了 _eventId 參數
這種方式一般用在 state 之間的 transition ,通過指定 _eventId 參數的值,表明了客戶的行為,從而導致相應事件的發生,在 Spring Web Flow 的定義文件中可以通過 evaluate 元素來指定要處理的業務邏輯。參看清單21:
清單 21 transition 示例
<transition on="submit">
<evaluate expression="validator.validate()" />
</transition>
清單 21 的代碼表示,當客戶端的請求中包含“ _eventId=submit ”,則 evaluate 元素中 expression 屬性所指明的表達式會被執行,即 validator 對象的validate 方法會得到調用。
執行到框架自定義的切入點
Spring Web Flow 定義了 5 個切入點,通過 flow 定義文件的配置,可在這 5 個切入點插入相關業務邏輯代碼。
表 2 Spring Web Flow 自定義的切入點
切入點名稱 XML 元素名稱 觸發時刻 flow start on-start flow 執行之前 state entry on-entry 進入某個 state 之後,做其他事情之前 view render on-render 在進入 view 的 render 流程之後,在 view 真正 render出來之前 state exit on-exit 在退出 state 之前 flow end on-end flow 執行結束之後
清單 22 給出了在 view render 切入點插入業務邏輯代碼的例子:
清單 22 on-render 元素
<view-state id="viewCart" view="viewCart" >
<on-render>
<evaluate expression="productService.getProducts()"
result="viewScope.products"/>
</on-render>
</view-state>
執行到 <action-state> 元素
Spring Web Flow 中的這個 <action-state> 是專為執行業務邏輯而設的 state 。如果某個應用的業務邏輯代碼即不適合放在 transition 中由客戶端來觸發,也不適合放在 Spring Web Flow 自定義的切入點,那麼就可以考慮添加 <action-state> 元素專用於該業務邏輯的執行。示例代碼參看清單23:
清單 23 action-state 示例
<action-state id="addToCart">
<evaluate expression="cart.addItem(productService.getProduct(productId))"/>
<transition to="productAdded"/>
</action-state>
業務邏輯代碼在調用後得到的數據如何保存、傳遞?
Spring Web Flow 的定義中可直接使用表達式語言( Expression Language ),前面的代碼都是用的 Unified EL ,對於習慣用 OGNL 的開發人員,可通過 flow-builder-services 的配置改成使用 OGNL 。不管是哪一種表達式語言, Spring Web Flow 都提供了一些固定名稱的變量,用於數據的保存、傳遞。在 Spring Web Flow 的解決方案 一節中,已經提到 Spring Web Flow 所著力解決的問題即是數據存取范圍的問題,為此, Spring Web Flow 提供了兩種比較重要的范圍,一是 flow 范圍,另一個是 conversation 范圍。通過 flowScope 和 conversationScope 這兩個變量, Spring Web Flow 提供了在這兩種范圍裡存取數據的方法。清單 24演示了如何將業務邏輯代碼執行的結果存放到flow范圍中。
清單 24 flowScope 示例
<evaluate expression="productService.getProducts()" result="flowScope.products" />
Spring Web Flow 還提供了大量其他的變量,以方便數據的存取。如 viewScope 范圍即是從進入 view-state 至退出 view-state 結束, requestScope 即和一般的 request 范圍沒什麼區別,等等。另外還有一些用於獲取 flow 以外數據的變量,如 requestParameters 、 messageContext 等等。具體變量的列表可參看 Spring Web Flow自帶的文檔。
為示例應用添加商品
接下來,我們要在示例應用的 viewCart.jsp 頁面中添加商品,可按以下步驟操作:
添加 Product 類
添加 ProductService 類
修改 shopping.xml 文件
修改 viewCart.jsp 頁面
添加 Product 類
Product 類是個普通的 JavaBean ,用於定義商品( Product )的一般屬性,同時也提供了構造方法。由於會把 Product 存放於 conversationScope 中, Product 實現了 Serializable 接口。具體見清單25:
清單 25 Product 類
package samples.webflow;
import java.io.Serializable;
public class Product implements Serializable {
private static final long serialVersionUID = 1951520003958305899L;
private int id;
private String description;
private int price;
public Product(int id, String description, int price) {
this.id = id;
this.description = description;
this.price = price;
}
/*省略getter和setter*/
}
添加 ProductService 類
ProductService 主要提供商品列表,並能根據商品的 id 查找出該商品,由於示例較簡單,這裡只添加了三條紀錄。見清單 26:
清單 26 ProductService 類
package samples.webflow;
/*省略import語句*/
@Service("productService")
public class ProductService {
/*products 用於存放多個商品 */
private Map<Integer, Product> products = new HashMap<Integer, Product>();
public ProductService() {
products.put(1, new Product(1, "Bulldog", 1000));
products.put(2, new Product(2, "Chihuahua", 1500));
products.put(3, new Product(3, "Labrador", 2000));
}
public List<Product> getProducts() {
return new ArrayList<Product>(products.values());
}
public Product getProduct(int productId) {
return products.get(productId);
}
}
Service 注解表示 Spring IoC 容器會初始化一個名為 productService 的 Bean ,這個 Bean 可在 Spring Web Flow 的定義中直接訪問。
修改 shopping.xml 文件
要在 viewCart 頁面中顯示商品,只需在 view-state 元素的 on-render 切入點調用 productService 的 getProducts 方法,並將所得結果保存到 viewScope 中即可。見清單27:
清單 27 shopping.xml 需修改的部分
<view-state id="viewCart" view="viewCart" >
<on-render>
<evaluate expression="productService.getProducts()" result="viewScope.products"/>
</on-render>
<transition on="submit" to="viewOrder"> </transition>
</view-state>
修改 viewCart.jsp 頁面
清單 27 表明 productService 的 getProducts 方法所得的結果會存放在 viewScope 中名為 products 的變量中, jsp 頁面的代碼可直接訪問該變量。見清單 28:
清單 28 修改後的 viewCart.jsp 頁面
<%@ 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>
<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>
</tr>
</c:forEach>
</table>
</body>
</html>
運行應用程序
圖 5 viewCart.jsp 頁面效果