Webwork2.2中加入了RIFE的Continuation引擎,但是目前这一部分还处于试验阶段,不能与用产品开发。
也许有些人还是头一次听说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