Spring Web Flow —— 第一個Flow - 010

在學習每一門新語言時,第一個程序往往是Hello World。這裏我們寫一個非常簡單的flow,使用常用標籤,在深入講解之前有一個感官上的認識

需求說明

假設有如下簡單流程:要求程序啓動,顯式輸入界面,用戶輸入信息後,點擊提交按鈕,後臺查詢數據庫,然後顯式查詢結果界面,中間任何步驟出錯,都重新返回輸入界面,並顯示錯誤信息。流程大體如下。
需求流程圖

需求分析

將上述需求分解:流程啓動時,初始化輸入界面的信息,渲染輸入頁面,用戶點擊提交按鈕,後臺驗證輸入信息的格式是否正確,驗證失敗則返回輸入界面,驗證成功則進行下一步查詢數據庫操作,查詢完成後跳轉到結果頁面進行顯示,流程結束。(請忽略如下流程中判斷圖示不標準的錯誤)
邏輯流程圖

運行前的基本配置

首先需要將FlowRegistry,FlowExecutor,FlowHandlerAdapter,FlowHandlerMapping等項配置好。本文的採用了Spring Web Flow 學習 —— 配置 - 001的配置。

Flow文件

如下配置文件講解:

  • 流程啓動時指定flowController的performInit()方法,並返回一個ModelMap類型的對象,分配flowScope作用域下的InitMap變量,將返回的對象賦予該變量;
  • 渲染/WEB-INF/jsp/flow/view/input.jsp,並將input界面中上傳的參數與searchForm進行綁定,當觸發submit事件時,跳轉到validate狀態
  • validate狀態中,執行flowController的performValidate(searchForm)方法,返回success時跳轉到result狀態,返回fail時跳轉到init狀態,重新渲染input.jsp。
  • result狀態,渲染result.jsp,渲染前,首先指定flowController的performSearch(searchForm)方法,該方法返回一個modelMap並分配給flashScope範圍內的modelMap變量。在result界面,無論點擊任何按鍵,只要是向flow在此提交請求,都會跳轉到end狀態,
  • end狀態, 重定向到spring首頁
    至此,流程結束。
<?xml version="1.0" encoding="UTF-8"?>
<!-- 這是web flow 2.4.5的根標籤形式,2.5.0並不一樣 -->
<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="searchForm" class="cn.floyd.pw.flow.SearchForm"/>

    <on-start>
        <evaluate expression="flowController.performInit()" 
                result="flowScope.InitMap" 
                result-type="org.springframework.ui.ModelMap"/>
    </on-start>

    <view-state id="init" view="flow/view/input" model="searchForm">
        <binder>
            <binding property="name"/>
            <binding property="gender"/>
        </binder>
        <transition on="submit" to="validate"/>
    </view-state>

    <action-state id="validate">
        <evaluate  expression="flowController.performValidate(searchForm)"/>
        <transition on="success" to="result"/>
        <transition on="fail" to="input"/>
    </action-state>

    <view-state id="result" view="flow/view/result">
        <on-render>
            <evaluate expression="flowController.performSearch(searchForm)" result="flashScope.modelMap"/>
        </on-render>
        <transition to="end"/>
    </view-state>

    <end-state id="end" view="externalRedirect:http://springframework.org"/>
</flow>

看完上面的描述,想必剛接觸web flow的人是一臉懵逼
這裏寫圖片描述
解釋幾個概念就好了。

  • 狀態:web flow將一個步驟稱作一個狀態(state),有專門渲染view的view-state,也有隻執行操作的action-state
  • 變量:web flow是以xml的形式進行編程的,可以在該xml上下文中定義變量,該變量可以在flow上下文以及flow渲染的jsp文件(使用EL表達式調用),調用的方法中(作爲參數傳入)使用。定義變量的方式主要有兩種,一是通過<var>標籤顯式定義,二是在<evaluate>標籤的result屬性中定義(此時同時完成了分配變量和變量賦值兩個操作)
  • 變量作用域:規定了變量的作用範圍,常見的有flashScope(當前狀態有效),flowScope(當前flow有效)等
  • 事件:從一個狀態到另一個狀態,一般需要進行觸發,而進行觸發的就是事件。事件可能由view觸發,也可能由方法觸發,我們不用真的去定義一個事件對象。當view-state中的view返回的請求中帶有_eventId的參數時,其值會被自動轉換成Event,當action-state中調用的方法返回字符串時,該字符串也會被自動轉換成Event
  • 模型綁定:當view需要提交參數時,可以採用模型綁定的形式,web flow會自動將對應參數綁定到我們的model中,並且還可以自定義驗證和轉換規則。

相關其它文件

FlowController

按照順序列出相關文件,<evaluate>標籤的expression屬性可以通過Spring EL表達式的形式直接訪問Spring管理的bean的方法或屬性,也可以訪問flow上下文環境中的對象的方法或屬性。其中主要方法講解如下:

  • performInit()初始化下拉選中的初始值,返回modelMap對象,該對象被賦值給flow中的InitMap變量
  • performValidate(SearchForm form)將SearchForm對象傳入,用於驗證輸入的值,這裏假設全都驗證通過,返回的”success”會被映射成Event
  • performSearch(SearchForm form)根據傳入form中的參數進行數據查找,這裏假設已經查找完畢,並將數據放入一個ModelMap,賦值給flow中的modelMap變量

該類是Spring管理的一個bean,

@Controller("flowController")
public class FlowController implements Serializable{

    private static final long serialVersionUID = -4439633424434338888L;

    public ModelMap performInit() {
        ModelMap model = new ModelMap();
        model.addAttribute("gender", new String[] {"Man", "Woman"});
        return model;
    }

    public String performValidate(SearchForm form) {
        // assume we have pass the validation
        return "success";
    }

    public ModelMap performSearch(SearchForm form) {
        // assume we have finished the search process, and got the search result
        ModelMap model = new ModelMap();
        model.addAttribute("name", form.getName());
        model.addAttribute("gender", form.getGender());
        model.addAttribute("age", 25);
        model.addAttribute("profession", "Programmer");
        model.addAttribute("hobbies", new String[] {"Basktball", "Football"});

        return model;
    }
}

input.jsp

輸入界面,將InitMap中的值通過EL表達式渲染到下拉選中。通過_eventId=submit的GET參數方式觸發submit事件。${flowExecutionUrl}是flow中自管理變量,訪問它才能使得流程繼續下去

  • 尤其需要注意的一個點是,在聲明form時,一定要使用spring提供的taglib,否則會出現無法跳轉到下一個state,無限回到第一個state的情況。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Input page</title>
    </head>
    <body>
        <!-- commandName 聲明該form需要綁定的model,與flow.xml中<view-state>聲明的model是一個東西 -->
       <sf:form action="${flowExecutionUrl}&_eventId=submit" commandName="searchForm" method="post">
           Name: <sf:input path="name"/><br/>
           gender:
           <sf:select path="gender">
               <sf:option value="">- Please Select -</sf:option>
               <c:forEach items="${InitMap.gender }" var="gender">
                   <sf:option value="${gender }">${gender }</sf:option>
               </c:forEach>
           </sf:select>
           <input type="submit" value="Submit">
       </sf:form>
    </body>
</html>

SearchForm

form,與input.jsp中的form參數對應,注意必須要實現序列化接口。

public class SearchForm implements Serializable {

    private String name;
    private String gender;

    ... ...
}

result.jsp

將flow中的modelMap變量渲染到頁面中

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Result Page</title>
    </head>
    <body>
        Name: ${modelMap.name } <br/>
        Gender: ${modelMap.gender } <br/>
        Age: ${modelMap.age } <br/>
        profession: ${modelMap.profession } <br/>
        hobbies: 
        <c:forEach items="${modelMap.hobbies }" var="hobby">
            ${hobby } &ensp;
        </c:forEach>
        <br/>
    </body>
</html>

調試中遇到的坑

  • 現象
    點擊form的submit按鈕,無法跳轉到下一個state,始終重新渲染input界面;定義一個<a href="${flowExecutionUrl}&_eventId=submit">標籤,能夠正常跳轉到下一個state
  • 原因
    使用了原生的html標籤<form>進行表單的聲明,導致web flow無法繼續工作,改用Spring標籤即可。
    Web Flow無法工作的具體原理尚不明晰,我是在參考公司已有項目時發現這個問題的。
  • 解決方案
    使用Spring官方提供的taglib進行form及其屬性的聲明。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章