也許有些人還是頭一次聽說Continuation,在看了Webwork2.2的相關資料前我也不知道。但是Continuation其實已經存在很長時間了,在RIFE和Concoon中都有相關的實現。
Continuation倒地是什么,有什么用呢?
想一下這樣一個經常遇到的需求:
第一個頁面:用戶填寫自己的信息,提交進入第二個頁面。
第二個頁面:用戶信息被列出來,等待用戶按確認保存,然后用戶按保存,把客戶信息存入數據庫。
這是一個經常遇到的需求,不用Continuation也是可以解決的:
對于基于MVC的Framwork(如webwork,struts),我們一般用Session或者Hidden來解決。但是Session放了東西忘了清空或者用戶直接更改URL,都會引來一堆麻煩。用Hidden比較保險,但是要在頁面里寫很多東西。
對于基于Component的Framework(如ASP.NET,Tapestry),也許簡單一些,把幾個畫面寫道一個頁面中,根據不同的步驟Render不同的部分。這樣數據保存的問題是沒有了,但是如果不是僅僅兩個畫面,而是5個或更多,那你的頁面文件就很難維護了。
有什么辦法能讓屬于同一個處理過程的不同頁面直接的數據傳遞很自然的進行,不通過session和不用hidden更不要在同一個文件中寫多個畫面。
沒有Continuation也能解決,我們公司以前的一個項目為了解決這個問題,自己寫了一些Session Listener來根據URL中processid參數的變化來自動管理Session中的東西,這樣程序員在處理這樣的邏輯的時候就不用關心session的清理了,在同一個processid過程中的session中東西是不會被清除的,一旦processid發生變化,就會自動清除session中相關的東西。
這樣做雖然在一定程度讓解決了跨頁面傳遞數據的session管理的問題,但是還很不靈活。但是現在webwork2.2中加入的Coninuation能解決我們的問題了,而且是以一種比較自然的方式來解決的。
先看一下,webwork給的例子:
public class Guess extends ActionSupport {
int guess;
public String execute() throws Exception {
int answer = new Random().nextInt(100) + 1;
int tries = 5;
while (answer != guess && tries > 0) {
pause(SUCCESS);
if (guess > answer) {
addFieldError("guess", "Too high!");
} else if (guess < answer) {
addFieldError("guess", "Too low!");
}
tries--;
}
if (answer == guess) {
addActionMessage("You got it!");
} else {
addActionMessage("You ran out of tries, the answer was " + answer);
}
return SUCCESS;
}
public void setGuess(int guess) {
this.guess = guess;
}
}
對于一個guess的整個過程其實應該屬于一個完整的人機會話,不應該用多個Action或者借助Session來保存中間數據。在這個例子中pause方法把這個action的處理暫停,然后等待用戶響應。直到用戶猜對或超過規定次數為止。以這種方式實現使得程序很自然。
下面我再給出上面我寫的那個需求的實現。
首先我們一個三個頁面:第一個輸入頁面;第二個現實輸入結果,等待確認;第三個現實確認結果。在這上個頁面的流轉過程中,我們既不用session也不用hidden。
step1.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="ww" uri="webwork" %>
<html>
<head><title>INPUT</title></head>
<body>
<ww:form action="default.action" method="post" >
<ww:textfield name="name" value="%{#name}" label="Name"/>
<ww:submit value="submit"/>
</ww:form>
</body>
</html>
step2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="ww" uri="webwork" %>
<html>
<head><title>CONFIRM</title></head>
<body>
<ww:property value="name"/>
<ww:form action="default.action" method="post">
<ww:submit value="confirm"/>
</ww:form>
</body>
</html>
step3.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="ww" uri="webwork" %>
<html>
<head><title>DONE</title></head>
<body>
<ww:property value="name"/>
<ww:form action="default.action" method="post">
<ww:submit value="done"/>
</ww:form>
</body>
</html>
當然還要一個Action
package org.mstar.webwork.chain;
import com.opensymphony.xwork.ActionSupport;
public class ChainAction extends ActionSupport {
private String name;
public String execute() throws Exception {
System.out.println("@@@@@@@---before start");
System.out.println("@@@@@@@---name :" + name);
pause("start");
System.out.println("@@@@@@@---after start");
System.out.println("@@@@@@@---name :" + name);
System.out.println("@@@@@@@---name --> myName");
String myName = name;//myName是個中間變量,必須是局部變量
System.out.println("@@@@@@@---myName :" + myName);
System.out.println("@@@@@@@---before next");
pause("next");
System.out.println("@@@@@@@---after next");
System.out.println("@@@@@@@---name :" + name);
System.out.println("@@@@@@@---myName :" + myName);
System.out.println("@@@@@@@---myName --> name");
name = myName;
System.out.println("@@@@@@@---name :" + name);
System.out.println("@@@@@@@---before done");
pause("done");
System.out.println("@@@@@@@---after done");
System.out.println("@@@@@@@---name :" + name);
System.out.println("@@@@@@@---myName --> name");
name = myName;
System.out.println("@@@@@@@---name :" + name);
System.out.println("@@@@@@@---before return");
return SUCCESS;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
這里面也寫了很多輸出語句,你可以根據這寫輸出感覺一下整個過程的處理情況。
然后配置webwork
xwork.xml
<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www.opensymphony.com/xwork/xwork-1.1.dtd">
<xwork>
<include file="webwork-default.xml"/>
<package name="default" extends="webwork-default">
<action name="default" class="org.mstar.webwork.chain.ChainAction">
<interceptor-ref name="defaultStack"/>
<result name="start">step1.jsp</result>
<result name="next">step2.jsp</result>
<result name="done">step3.jsp</result>
<result name="success">step1.jsp</result>
</action>
</package>
</xwork>
webwork.properties
webwork.continuations.package=org.mstar.webwork.chain
這里的webwork.properties一定要配置,否則continuation不會起作用。
主要的文件就是這樣了,正確的情況應該是在第一個頁面輸入的東西在第二個頁面和第三個頁面都能呈現出來。
注意整個過程我們沒有接觸到session也沒有接觸的倒hidden,代碼別寫也更接近于一次很自然的會話。
這里一個神奇的元素就是pause,這個方法的參數是xwork中配置的Result。在運行到pause時Action會中斷,然后返回到頁面等待響應,相應完成后(頁面上的提交又指向這個Action,并且帶有上次中斷時生成的ContinuationID作為參數),程序會接著上次中的pause方法下面開始運行,直到Action返回。
更加神奇的是如果你開xwork的ActionSupport.java,你會驚奇的發現pause方法竟然是空的,沒有實現的。(第一次可把我嚇一跳),后來看了ww wiki上的介紹才知道:對于需要Continuation功能的ActionSupport類ww會更具我們寫的這個ActionSupport生成一個(或者幾個吧,具體我還沒仔細研究過)具有Continuation能力的ActionSupport來完成我們的要求。
雖然Continuantion是個好東西,但是現在webwork的Continuation還在試驗階段,可能還存在著一些問題,比如會不會影響性能,會不會對其他的攔截器造成影響都還未知。不過這種解決方法肯定是一種趨勢,我也希望webwork找點把這個功能完善。具有ajax能力和Continuation能力的Webwork絕對是超可愛的MM。
詳細信息參見:http://wiki.opensymphony.com/display/WW/Continuations
源碼下載:ftp://zjumty.3322.org/TEMP/Chain.rar

]]>