分享

WebWork教程-0.90版

 思奇剑 2006-11-10

WebWork介绍

WebWork是由OpenSymphony组织开发的,致力于组件化和代码重用的拉出式MVC模式J2EE Web框架。WebWork目前最新版本是2.1,现在的WebWork2.x前身是Rickard Oberg开发的WebWork,但现在WebWork已经被拆分成了Xwork1WebWork2两个项目,如下示意图所示:

 

 

WebWork1

XWork1

WebWork2

 

Web

Non-web

 

 

 

 

 

 

 

 

 


Xwork简洁、灵活功能强大,它是一个标准的Command模式实现,并且完全从web层脱离出来。Xwork提供了很多核心功能:前端拦截机(interceptor),运行时表单属性验证,类型转换,强大的表达式语言(OGNL – the Object Graph Notation Language),IoCInversion of Control倒置控制)容器等。

WebWork2建立在Xwork之上,处理HTTP的响应和请求。WebWork2使用ServletDispatcherHTTP请求的变成Action(业务层Action), session(会话)application(应用程序)范围的映射,request请求参数映射。WebWork2支持多视图表示,视图部分可以使用JSP, Velocity, FreeMarker, JasperReportsXML等。

下面我们提到的WebWork将为WebWork2,使用的版本是2.1

 

WebWork安装-HelloWorld

WebWork安装

当然,在具体开发使用介绍之前,搭建好运行环境是必备的。

首先从https://webwork.dev./servlets/ProjectDocumentList下载最新的WebWork压缩包,并将其解压开来。打开解压目录,你将看到以下的文件和目录:

webwork-2.x.jar 当然就是WebWrok最新发布的Jar

webwork-example.war WebWrok自带的很有代表性的功能演示例子,掌握它是提高你的WebWork技术水平的捷径

webwork-migration.jar 提供快速将1.x版本移植到2.x版本所用的类文件

docs目录 WebWrok的使用文档,包括api文档、clover文档、单元测试(Junit)文档等

lib目录 WebWork在运行或编译时所用到的所有.jar

src目录 源程序目录

2WebWorkJ2EE Web框架,当然要运行在Web容器中,我用的是稳定的Tomcat 4.1,关于tomcat的安装和部署请自己搞定。

3、用WebWork当然要将它的运行时用到的Jar包放到Web容器可以找到的ClassPath中,将步骤1介绍的webwork-2.x.jar放到你部署目录下WEB-INF\lib目录里,同时将WebWrok解压目录lib\core下的所有.jar文件也拷贝到WEB-INF\lib目录,这些是运行WebWork必需要用到的jar包。

4、了解Web框架的朋友都知道,一般Web框架都是通过一个JavaServlet控制器提供统一的请求入口,解析请求的url,再去调用相应的Action进行业务处理。WebWork也不例外,它要求你在web.xml文件里配置一个派遣器ServletDispatcher,它初始化WebWrok的一些配置信息,解析XWorkAction配置信息,根据请求去组装和调用执行相应的拦截器(Interceptor)ActionAction Result(Action执行结果的输出)等,具体配置如下:

……

<servlet>

<servlet-name>webwork</servlet-name>

<servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatcher</servlet-class>

</servlet>

……

<servlet-mapping>

<servlet-name>webwork</servlet-name>

<url-pattern>*.action</url-pattern>

</servlet-mapping>

……

这样,.action结尾的所有url请求将直接有ServletDispatcher去调度。下面我们写一个经典的HelloWorld,跑一个简单实例来验证你运行环境是否可用,并感受一下简单、功能强大的WebWork的开发。

注意:如果使用WebWork自带的标签库,除了配置相应的标签库以外,还须将com.opensymphony.webwork.views.velocity.WebWorkVelocityServlet配置到web.xml,具体可以参考webwork-example里面的配置。

HelloWorld

首先看下面这个程序HelloWorldAction.java

package helloWorld

 

import com.opensymphony.xwork.Action;

 

public class HelloWorldAction implements Action{

 

    String greeting;

 

    public String getGreeting() {

        return greeting;

    }

 

    public String execute() throws Exception {

        greeting = "Hello World!";

        return SUCCESS;

    }

 

}

HelloWorldAction是一个普通的Java类,它实现了Action这个接口。Action是一个非常简单的接口,只有一个方法:public String execute() throws Exception; Action类介绍见下一节。HelloWorldAction有一个String类型字段greeting,在execute()方法中,greeting被赋值“Hello World!”,并返回String型常量SUCCESSSUCCESS的定义详见Action接口,这个常量代表了execute()方法执行成功,将返回成功页面。

返回的页面greetings.jsp代码如下:

<%@ taglib prefix="ww" uri="webwork" %>

<html>

<head>

    <title>First WebWork Example</title>

</head>

<body>

<p><ww:property value="greeting"/></p>

</body>

</html>

    greetings.jsp很简单的jsp页面,它使用了WebWork自带的标签库。它的作用是输出变量“greeting”的值。这个<ww:property value="greeting"/>语句,相当于调用相应ActionHelloWorldAction)的getGreeting()方法,取得变量“greeting”的值。

我们的HelloWorld代码就这么多,完了。可是,HelloWorldAction怎么去调用、执行?执行成功它又怎么知道返回到greetings.jspXWork的配置文件xwork.xml会负责将要执行的Action和展现的视图连接起来,见xwork.xml的如下片断:

<action name="hello" class=" helloWorld .HelloWorldAction">

    <result name="success" type="dispatcher">

       <param name="location">/greetings.jsp</param>

    </result>

</action>

我们先看action标签:name=”hello”,表示我们调用这个Action的标识是hello,这样我们可以通过下面的url访问这个Action…/hello.action,
例如:http://localhost:8080/webwork/hello.actionclass=" helloWorld .HelloWorldAction"很好理解,这是真正调用执行的类。我们在看看result标签:name="success",记得前面HelloWorldAction返回的字符常量SUCCESS吗?它的值其实就是“success”,它表示Action执行成功返回success就转向这个结果;type="dispatcher"表示执行完Action,转向结果页面的方式;param参数指定了结果页面的位置:/greetings.jsp

代码写完,剩下的当然是编译、部署。启动tomcat服务器之后我们就可以执行了:

在浏览器里输入你的地址:http://localhost:8080/webwork/hello.action

你将会看到如下结果:

Action(动作)

Action介绍

ActionMVC模式中担任控制部分的角色,在WebWork中使用的最多。每个请求的动作都对应于一个相应的Action,一个Action是一个独立的工作单元和控制命令,它必需要实现XWork里的Action接口,实现Action接口的execute()方法。Action接口的代码如下:

 

package com.opensymphony.xwork;

 

import java.io.Serializable;

 

public interface Action extends Serializable {

   

    public static final String SUCCESS = "success";

    public static final String NONE = "none";

    public static final String ERROR = "error";

    public static final String INPUT = "input";

    public static final String LOGIN = "login";

   

    public String execute() throws Exception;

}

excute()方法是Action类里最重要的部分,它执行返回String类型的值,在Action中返回的值一般使用它上面定义的标准静态字符常量。例如:前面的HelloWorldAction返回的就是SUCCESS字符常量,真正的值当然就是“success”,它与xwork配置文件里result标签name的值是相对应的。它用来决定execute()方法执行完成之后,调用哪一种返回结果。字符常量的含义如下:

SUCCESSAction正确的执行完成,返回相应的视图;

NONE:表示Action正确的执行完成,但并不返回任何视图;

ERROR:表示Action执行失败,返回到错误处理视图;

INPUTAction的执行,需要从前端界面获取参数,INPUT就是代表这个参数输入的界面,一般在应用中,会对这些参数进行验证,如果验证没有通过,将自动返回到该视图;

LOGINAction因为用户没有登陆的原因没有正确执行,将返回该登陆视图,要求用户进行登陆验证。

用户注册例子

下面我们将以一个用户注册的例子详细介绍Action的原理:

功能描述:一个用户注册页面register.jsp,用户可以在这个页面里输入用户注册的基本信息(例如:姓名、密码、Email等),输入完成提交表单,执行用户注册的Action,执行成功返回成功提示的页面(register-result.jsp)并将注册的信息输出。

模型:User.java            

控制:RegisterAction.java

视图:register.jspregister-result.jsp

配置:xwork.xml

User.java

package register;

 

public class User {

   

    private String username;

    private String password;

    private String email;

    private int age;

 

    public String getUsername() {

        return username;

    }

 

    public void setUsername(String username) {

        this.username = username;

    }

……

   

    public int getAge() {

        return age;

    }

 

    public int setAge(int age) {

        this.age = age;

    }

   

    public String toString(){

    return "username=" + username

        + ";password=" + password

        + ";email=" + email

        + ";age=" + age;

    }

}

模型User是一个普通的JavaBean,它包含了用户注册的字段信息,并对每个字段提供相应的setget方法。下面我们来看看进行用户注册动作的RegisterAction.java

package example.register;

 

import com.opensymphony.xwork.Action;

 

/**

 * @author moxie-qac

 *         achqian@yahoo.com.cn

 */

public class RegisterAction implements Action {

   

    private User user= new User();

 

    public User getUser(){

       return this.user;

    }

   

    public String execute(){

      

       System.out.println("Start execute 。。。。。。。。。。。。。");

       System.out.println("User="+user);

       //在这里调用用户注册的业务逻辑,比如:将注册信息存储到数据库

   

       return SUCCESS;

    }

}

这个Action是不是特清爽?用户注册就这么几行代码搞定,当然,我们提倡在Action里最好不要实现业务代码,Action的主要功能是提供从请求中取得参数的值,转化成相应的模型,再将模型传递给执行业务操作的对象,比如:将注册的用户信息存储到数据库中,由业务对象执行业务操作,再返回执行的结果。为了简化我们省去了注册的业务逻辑执行步骤。

再看看我们注册信息输入的页面:register.jsp

<html>

<head><title>Register Example</title></head>

<body>

<table border=0 width=97%>

<tr><td align="left">

    <form name="register" action="register.action" method="post">

       Username:<input type="text" name="user.username"><br>

       Password:<input type="text" name="user.password"><br>

       Email:<input type="text" name="user.email"><br>

       Age:<input type="text" name="user.age"><br>

       <input type="submit" name="Submit"><br>  

    </form>

</td></tr>

</table>

</body>

</html>

register.jsp页面其实只是一个普通的HTML页面,它提供了一个表单,用来接受用户输入的注册信息,它唯一特殊的部分就是input输入框定义的name部分,例如:用户姓名用的是“user. username”。这种命名方式代表什么含义?它是必需的吗?后面我们将会给出答案。

RegisterAction正确执行完成之后,会将执行的结果返回到register-result.jsp页面,由它来显示用户在前面页面输入的注册信息。register-result.jsp代码如下:

<%@ taglib prefix="ww" uri="webwork" %>

<html>

<head><title>Register result</title></head>

<body>

    <table border=0 width=97%>

       <tr>

           <td align="left">

           Congratulation,your register success!<p>

           Username:<ww:property value="user.username"/><br>

           Password:<ww:property value="user.password"/><br>

           Email:<ww:property value="user.email"/><br>

           Age:<ww:property value="user.age"/><br>

           </td>

       </tr>

    </table>

</body>

</html>

这个Jsp页面使用了WebWork的标签库 <ww:property />,记得HelloWorld里的greetings.jsp吗?它也使用了这个标签库。我们看这个:<ww:property value="user.username"/>
它是一个普通的使用标签库语句,查看这个标签库的源程序,见包
com.opensymphony.webwork.views.jsp
里的PropertyTag.java文件,你会发现这个类会根据value后面赋予的表达式值,去OgnlValueStack里查找这个表达式值所对应的操作。执行这个语句OgnlValueStack会根据value的值(一个表达式)“user.username”去分别调用RegisterAction类的getUser()User类的getUsername()方法,即:getUser().getUsername(),取得的数据就是前面注册页面输入的用户名。

我们把“user.username”这样的语句叫做表达式语言(Expression Language,简称为EL)。它由XWork框架提供,XWork表达式语言的核心是OGNLObject Graph Notation Language),OGNL是一种功能强大,技术成熟,应用广泛的表达式语言,将在下面的章节有详细介绍。

我们在回到前面介绍的register.jspInput输入框
<input type="text" name="user.username">
里用的“user.username”,现在我们可以明白,它不是随意设置的,它是一个表达式语言,有着特殊的功能。看到这里,不知道你心中是否有一个疑问:我们的RegisterAction是如何取得用户注册页面输入的数据呢?如果你做过Web开发,你一定会想到RegisterAction里必需有一些从客户端请求中获取参数的语句,例如: 类似:String username = request.getParameter(“user. username”)的语句(requestHttpServletRequest的对象),去从request请求里面获取用户输入的参数值。可是我们这个Action里面只有User对象简单的get方法,并没有其它的代码。Xwork框架的Action是如何去实现了与Web无关?request请求的参数是怎么传递到我们Action的模型User中呢?

在回答答案之前,我们先看一看Xwork的配置文件xwork.xml

                                                                              

<action name="register" class="example.register.RegisterAction" >

       <result name="success" type="dispatcher">

           <param name="location">/register-result.jsp</param>

       </result>

       <interceptor-ref name="params"/>

</action>

看了前面的介绍,这段配置文件应该不难理解。用户通过注册页面register.jsp输入自己的注册信息,提交表单到动作register.action,它将有ServletDispatcher调度,从配置文件xwork.xml里查找与“register”匹配的Action名字,即上面配置的Action。通过这个名字XWork框架找到这个Action的类:example.register.RegisterActionXWork框架会负责去创建这个Action类的对象并调用execute()方法进行用户注册操作。正确执行execute()方法返回String类型数据“success”之后,它会请求再派遣到register-result.jsp页面。

在这段配置文件里,你一定注意到了它特殊的一句:<interceptor-ref name="params"/>interceptor-ref标签设置这个Action用到的拦截器(Interceptor),“params”引用的是配置文件中的<interceptor name="params" class="
com.opensymphony.xwork.interceptor.ParametersInterceptor"/>
,这个拦截器将在RegisterActionexecute()方法执行之前调用,作用是将request请求的参数值通过表达式语言设置到相应RegisterAction的模型里。例如:register.jsp里的<input type="text" name="user.username">,它输入的值会由RegisterAction类的getUser()User类的setUserName(“…”)设置到这个User模型里。假设你在注册页面输入用户名“moxie”,提交表单ParametersInterceptor就会下面的操作:首先从请求中取得参数的名字和名字对应的值,分别为:“user.username”和“moxie”,根据这个名字,从OgnlValueStack中取得堆栈最上面的getUser().setUsername(“moxie”)操作,即取得RegisterAction对象的User模型,并设置username属性的值为“moxie”。

原来,我们的Action是通过XWork的拦截器ParametersInterceptor从提交的表单中取得请求的参数和值,再通过OgnlValueStack来执行表达式,调用Action和模型里相应的geset方法,将从请求中取得的值设置到模型中去。register.jspInput输入框的name="user.username"是必需要遵守OGNL的命名规则。也正是很多拦截器的使用,使得我们的Action类和Web实现了完全的解耦,让我们的Action能如此的简单、优雅,拦截器的原理后面章节我们也将会有详细的介绍。

罗索了这么多,你一定是精通了这个用户注册的例子了吧!呵呵!

Field-Driven Action vs. Model-Driven Action

Action根据FormBean的不同可以分为二类,

一类是Field-Driven(字段驱动的)Action

   Action将直接用自己的字段来充当FormBean的功能,我们的例子就是使用这种方式。它一般用在页面表单比较简单的情况使用,而且可以直接用域对象作为Action的字段,这样就不用在另写FormBean,减少了重复代码。

另一类是Model-Driven(模型驱动的)Action

它很像StrutsFormBean,但在WebWork中,只要普通Java对象就可以充当模型部分。Model-Driven(模型驱动的)Action要求我们的Action实现com.opensymphony.xwork. ModelDriven接口,它有一个方法:Object getModel();,我们用这个方法返回我们的模型对象就可以了。

我们可以将前面的RegisterAction.java改为Model-Driven(模型驱动的)Action

package example.register;

 

import com.opensymphony.xwork.Action;

import com.opensymphony.xwork.ModelDriven;

 

/**

 * @author moxie-qac

 *         achqian@yahoo.com.cn

 *

 */

public class RegisterActionModel implements Action,ModelDriven{

    private User user = new User();

   

    public String execute() throws Exception {

       System.out.println("Start execute......。。。。。。。。。。。。。。");

       System.out.println("User="+user);

       //在这里调用用户注册的业务逻辑,比如:将注册信息存储到数据库

   

       return SUCCESS;

    }

   

    public Object getModel() {

       return user;

    }

}

这时我们输入信息的页面也有了变化:register-model.jsp

<html>

<head><title>Register Example</title></head>

<body>

<table border=0 width=97%>

<tr><td align="left">

    <form name="register" action="registerModel.action" method="post">

       Username:<input type="text" name="username"><br>

       Password:<input type="text" name="password"><br>

       Email:<input type="text" name="email"><br>

       Age:<input type="text" name="age"><br>

       <input type="submit" name="Submit"><br>  

    </form>

</td></tr>

</table>

</body>

</html>

我们发现,输入框里的命名发生了变化。它们都少了“user.”这部分信息。

当我们采用Model-Driven(模型驱动的)Action时,它将取得模型对象保存在值堆栈中。“name="username"”就是代表直接调用模型对象的setUsername()方法。

我们Action的在配置文件中,也要给它指定一个拦截器model-driven它的作用就是将模型对象保存到值堆栈中。关于拦截器的介绍请看下面的章节。

配置文件如下:

<action name="registerModel" class="example.register.RegisterActionModel">

       <result name="success" type="dispatcher">

           <param name="location">/register-result-model.jsp</param>

       </result>

       <interceptor-ref name="model-driven"/>

       <interceptor-ref name="params"/>

    </action>

ActionContext(Action上下文)

ActionContext介绍

通过上面用户注册例子的学习,我们知道XworkWeb无关性,我们的Action不用去依赖于任何Web容器,不用和那些JavaServlet复杂的请求(Request)、响应(Response)关联在一起。对请求(Request)的参数(Param),可以使用拦截器框架自动调用一些get()set()方法设置到对应的Action的字段中。但是,仅仅取得请求参数的值就能完全满足我们的功能要求吗?不,在Web应用程序开发中,除了将请求参数自动设置到Action的字段中,我们往往也需要在Action里直接获取请求(Request)或会话(Session)的一些信息,甚至需要直接对JavaServlet Http的请求(HttpServletRequest)、响应(HttpServletResponse)操作。

带着这些问题,我们来看看下面的一个功能需求:

我们需要在Action中取得request请求参数“username”的值:

   ActionContext context = ActionContext.getContext();

Map params = context.getParameters();

String username = (String) params.get(“username”);

为了实现这个功能,我们用了三个步骤:

1、 取得我们当前的ActionContext对象contextActionContext是个什么冬冬?

2、 context对象里获取我们所有的请求参数,取得的却是一个Map对象params

3、 居然可以从我们的Map对象params里获取我们需要的request请求参数“username”的值。

ActionContextcom.opensymphony.xwork.ActionContext)是Action执行时的上下文,上下文可以看作是一个容器(其实我们这里的容器就是一个Map而已),它存放放的是Action在执行时需要用到的对象,比如:在使用WebWork时,我们的上下文放有请求的参数(Parameter)、会话(Session)Servlet上下文(ServletContext)、本地化(Locale)信息等。

在每次执行Action之前都会创建新的ActionContextActionContext是线程安全的,也就是说在同一个线程里ActionContext里的属性是唯一的,这样我的Action就可以在多线程中使用。

我们可以通过ActionContext的静态方法:ActionContext.getContext()来取得当前的ActionContext对象,我们看看这段代码:

public static ActionContext getContext() {

        ActionContext context = (ActionContext) actionContext.get();

 

        if (context == null) {

            OgnlValueStack vs = new OgnlValueStack();

            context = new ActionContext(vs.getContext());

            setContext(context);

        }

 

        return context;

    }

一般情况,我们的ActionContext都是通过:ActionContext context = (ActionContext) actionContext.get();来获取的。我们再来看看这里的actionContext对象的创建:static ThreadLocal actionContext = new ActionContextThreadLocal();ActionContextThreadLocal是实现ThreadLocal的一个内部类。ThreadLocal可以命名为“线程局部变量”,它为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。这样,我们ActionContext里的属性只会在对应的当前请求线程中可见,从而保证它是线程安全的。

下面我们看看怎么通过ActionContext取得我们的HttpSession

Map session = ActionContext.getContext().getSession()

原来我们取得的session却是Map类型的对象,这是为什么?原来,我们的WebWork框架将与Web相关的很多对象重新进行了包装,比如这里就将HttpSession对象重新包装成了一个Map对象,供我们的Action使用,而不用直接和底层的HttpSession打交道。也正是框架的包装,让我们的Actoion可以完全的和Web层解藕。

如果我们的Action需要直接与JavaServletHttpSessionHttpServletRequest等一些对象进行操作,我们又该如何处理?请看下面的ServletActionContext

ServletActionContext

ServletActionContextcom.opensymphony.webwork. ServletActionContext),这个类直接继承了我们上面介绍的ActionContext,它提供了直接与JavaServlet相关对象访问的功能,它可以取得的对象有:

1、 javax.servlet.http.HttpServletRequestHTTPservlet请求对象

2、 javax.servlet.http.HttpServletResponse;HTTPservlet相应对象

3、 javax.servlet.ServletContextServlet 上下文信息

4、 javax.servlet.ServletConfigServlet配置对象

5、 javax.servlet.jsp.PageContextHttp页面上下文

ServletActionContext除了提供了上面这些对象访问,它当然也继承了它父类ActionContex的很多功能,比如:对OgnlValueStackAction名字等的访问。

下面我们看看几个简单的例子,让我们了解如何从ServletActionContext里取得JavaServlet的相关对象:

1、 取得HttpServletRequest对象:

HttpServletRequest request = ServletActionContext. getRequest();

2、 取得HttpSession对象:

HttpSession session = ServletActionContext. getRequest().getSession();

ServletActionContextActionContext有着一些重复的功能,在我们的Action中,该如何去抉择呢?我们遵循的原则是:如果ActionContext能够实现我们的功能,那最好就不要使用ServletActionContext,让我们的Action尽量不要直接去访问JavaServlet的相关对象。在使用ActionContext时有一点要注意:不要在Action的构造函数里使用ActionContext.getContext(),因为这个时候ActionContext里的一些值也许没有设置,这时通过ActionContext取得的值也许是null

 

 

 

ServletDispatcher原理

ServletDispatcher是默认的处理Web Http请求的调度器,它是一个JavaServlet,是WebWork框架的控制器。所有对Action调用的请求都将通过这个ServletDispatcher调度。它将在web.xml里配置ServletDispatcher时指定,让所有对WebWork Action(默认的是.action的后缀)的请求都对应到该调度的JavaServlet中,具体配置在前面的WebWork安装中有介绍。

ServletDispatcher接受客户端的HTTP请求,将JavaServlet的很多相关对象进行包装,再传给我们的XWork框架,由我们的XWork框架去解析我们的xwork.xml配置文件,根据配置文件的信息,创建对应的Action,组装并调用相应的拦截器,执行Action,返回执行结果。WebWork使用XWork的核心,主要是由这个ServletDispatcher去实现的,

 

ServletDispatcher的主要功能调用如下:

一、init()方法在服务器启动时调用,

   1、初始化Velocity引擎

2、检查是否支持配置文件重新载入功能。如果webwork.configuration.xml.reload(见webwork.properties文件)设置为true,每个request请求都将重新装载xwork.xml配置文件。在开发环境使用将会非常方便,但在生产环境必需设置为false

代码如下:

if ("true".equalsIgnoreCase(Configuration.getString("webwork.configuration.xml.reload"))) {

FileManager.setReloadingConfigs(true);

}

 3、设置一些文件上传的信息,比如:上传临时目录,上传的最大字节等。都设置在webwork.properties文件里,如果在classpath中找不到这个属性文件,它会去读取默认的default.properties

 

二、service()方法,每次客户端的请求都将调用此方法。

 1通过request请求取得action的命名空间(namespace,与xwork.xml配置文件里package标签的name对应)

 例如:/foo/bar/MyAction.action,取得的命名空间为/foo/bar

xwork.xml配置文件里应该有这一段:

<package name="foo.bar" …….

 2根据servlet请求的Path,解析出要调用该请求的Action的名字(actionName),例如:(../foo/bar/MyAction.action -> MyAction

xwork.xml配置文件里应该有:

<package name="foo.bar" …….

       <Action name=” MyAction”……

 

3、 创建Action上下文(extraContext)。我们前面介绍的ActionContext上下文的对象,就是在这里设置的。它将JavaServlet相关的对象进行包装,放入到extraContext这个Map对象里。

/**

     * 将所有的应用请求和servlet属性保存到一个HashMap中,

    * @param requestMap 存放所有request请求属性的Map

     * @param parameterMap 存放所有request请求参数的Map

     * @param sessionMap存放所有session属性的Map

     * @param applicationMap 存放所有servlet上下文属性的Map

     * @param request HttpServletRequest 对象

     * @param response  HttpServletResponse 对象.

     * @param servletConfig  ServletConfig 对象.

     * @return代表Action 上下文的一个 HashMap

     */

public static HashMap createContextMap(Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response, ServletConfig servletConfig) {

        HashMap extraContext = new HashMap();

        extraContext.put(ActionContext.PARAMETERS, parameterMap);

        extraContext.put(ActionContext.SESSION, sessionMap);

        extraContext.put(ActionContext.APPLICATION, applicationMap);

        extraContext.put(ActionContext.LOCALE, request.getLocale());

 

        extraContext.put(HTTP_REQUEST, request);

        extraContext.put(HTTP_RESPONSE, response);

        extraContext.put(SERVLET_CONFIG, servletConfig);

        extraContext.put(COMPONENT_MANAGER, request.getAttribute("DefaultComponentManager"));

 

        // helpers to get access to request/session/application scope

        extraContext.put("request", requestMap);

        extraContext.put("session", sessionMap);

        extraContext.put("application", applicationMap);

        extraContext.put("parameters", parameterMap);

 

        AttributeMap attrMap = new AttributeMap(extraContext);

        extraContext.put("attr", attrMap);

 

        return extraContext;

}

下面我们来看看它是如何将request请求的参数和session进行包装的:

protected Map getParameterMap(HttpServletRequest request) throws IOException {

        return request.getParameterMap();

}

这个方法比较简单,它直接调用了HttpServletRequest的方法getParameterMap(),将所有request请求的参数封装到一个Map中。

 

protected Map getSessionMap(HttpServletRequest request) {

        return new SessionMap(request);

}

这个方法取得所有Session中的属性,它调用了com.opensymphony.webwork.dispatcher. SessionMap类,这个类实现了Map接口,在entrySet()方法中列举Session的所有属性,存放在Set中。

 

4根据前面获得的namespaceactionNameextraContext,创建一个ActonProxy

    ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);

默认的proxycom.opensymphony.xwork.DefaultActionProxy,在它的构造函数会进行下面的操作:1)、根据namespaceactionName读取xwork.xml配置文件里这个Action的所有配置信息。

2)、创建ActionInvocation

invocation = ActionProxyFactory.getFactory().createActionInvocation(this, extraContext);

默认的invocationcom.opensymphony.xwork.DefaultActionInvocation,它的构造函数操作有:

a)         com.opensymphony.xwork.ObjectFactory创建我们配置文件描述的Action对象。再将这个Action对象存放入OgnlValueStack中。记得我们前面用户注册的例子吗?当用户提交表达时它会有表达式语言向OgnlValueStack取得Action对象的字段,再把输入框的数据设置到对应的Action字段中,这个Action对象就是在这个时候进栈的。

b)        传入extraContext参数,创建与ActionInvocation对应的Action上下文(ActionContext)。记得我们在介绍ActionContext的最后,提出了一个需要注意的地方:不要在Action构造函数中调用ActionContext.getContext()。现在应该能明白,原来是Action对象实例在ActionContext对象实例之前创建的,所有这样取得ActionContext容器对象就有可能会返回null

c)        取得这个Action对应的所有拦截器(Interceptor),存放入java.util.Iterator对象中。

 

5执行proxyexecute()方法,这个方法最核心的语句是:retCode = invocation.invoke(); invocation对象的invoke()方法它遍历并执行这个Action对应的所有拦截器,执行Action对应的方法(默认的是execute()),根据Action执行返回的值去调用执行相应的Result(返回结果处理)的方法。

Action的单元测试

理解了ServletDispatcher,我们就明白了整个框架调用执行的顺序。Action虽然是与Web无关,可是它的创建、参数设置、执行与我们的WebWorkXWork紧密关联在一起,有我们的控制器ServletDispatcher去统一调度,那我们如何去对Action进行独立的单元测试呢?

请看下面的例子:使用单元测试框架JUnitregister.User. RegisterAction做单元测试

example.register. RegisterActionTesttestExecuteWithProxyFactory()方法:

 

public void testExecuteWithProxyFactory() throws Exception{

      

       Map params = new HashMap();

       params.put("user.username","Moxie");

       params.put("user.password","mypassword");

       params.put("user.email","achqian@yahoo.com.cn");

       params.put("user.age",new Integer(23));

       Map extraContext = new HashMap();

       extraContext.put(ActionContext.PARAMETERS,params);

      

       ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy("example", "register", extraContext);

       proxy.setExecuteResult(false);

       assertEquals(proxy.execute(),"success");

      

       RegisterAction action = (RegisterAction) proxy.getAction();

       assertEquals(action.getUser().getUsername(),"Moxie");

       assertEquals(action.getUser().getAge(),23);

    }

下面解说这个方法:

1、 对象params表示请求参数的Map,在它里面设置了注册用户的信息。extraContext当然就是我们ActionContext上下文的容器,它里面保存了放置请求参数的对象params

2、 创建我们的ActionProxy,它传入的参数有:“example”-这个Action的命名空间,“register”-Action对应的名字,extraContext-存放Actin上下文里的对象,,执行并将它返回的值与“success”比较,测试Action是否能正确执行完成。注意:proxy.setExecuteResult(false);,因为我们是单元测试,所以Action执行完成就可以了,不用再去调用结果响应的操作,故将是否执行结果设置为“false”。

3、 Action正确执行完成之后,我们也可以测试现在Action的字段里的数据是否按照我们预期的要求正确设置。从ActionProxy对象里取得执行的Action,即RegisterAction对象,再取得它的User模型,将其数据与前面设置参数的数据进行比较,判断它是否等于我们预期设置的数值。

   

Result Type

前面我们学习了ServletDispatcher,它是WebWork框架机制的核心。它和Action在我们MVC模式中,扮演着控制器的角色,MVC模式通过控制器实现了我们模型和视图的分离。WebWork提供了多种活灵活视图展现方式。

我们先看看前面用户注册例子的展现方式:我们使用的是JspWebWork自带的标签库,Action对应的视图当然是在xwork.xml配置文件里设置:

<action name="register" class="example.register.RegisterAction" >

       <result name="success" type="dispatcher">

              <param name="location">register-result.jsp</param>

       </result>

       <interceptor-ref name="params"/>

</action>

ResultAction执行完返回的一个字符串常量,它表示Action执行完成的状态,比如:执行成功、执行失败等。在我们前面Action的介绍中,详细介绍了它默认的标准Result,当然Result我们也可以自己定义,只要是一个字符串常量就可以了。

Result的值在xwork.xml配置文件里就是result标签里“name”的值,name="success"表示Action执行成功,返回“success”就对应此标签的配置,进行视图输出。

type”就是我们的Result TypeResult Type是一个类,它在Action执行完成并返回Result之后,决定采用哪一种视图技术,将执行结果展现给用户。我们输出的类型是:type="dispatcher",它对应com.opensymphony.webwork.dispatcher.ServletDispatcherResult这个类,它将执行结果通过javax.servlet.RequestDispatcherforward()include()方法调度到Jsp页面展现。

我们可以自己开发Result Type,实现我们需要的视图展现方式。Result Type必需要实现com.opensymphony.xwork..Result接口。在WebWork中,它已经为我们提供了很多Result Type,实现了视图部分对JSP, Velocity, FreeMarker, JasperReportsXML等的支持,具体如下表格:

 

 

 

 

 

 

Result Type

Nname

Class

Dispatcher

dispatcher

com.opensymphony.webwork.dispatcher.ServletDispatcherResult

Redirect

redirect

com.opensymphony.webwork.dispatcher.ServletRedirectResult

Action Chaining

chain

com.opensymphony.xwork.ActionChainResult

Velocity

velocity

com.opensymphony.webwork.dispatcher.VelocityResult

FreeMarker

freemarker

com.opensymphony.webwork.views.freemarker.FreemarkerResult

JasperReports

jasper

com.opensymphony.webwork.views.jasperreports.JasperReportsResult

XML/XSL

xslt

com.opensymphony.webwork.views.xslt.XSLTResult

HttpHeader

 

com.opensymphony.webwork.dispatcher.HttpHeaderResult

 

Dispatcher通过javax.servlet.RequestDispatcherforward()include()方法调度到页面展现,这样的页面一般是Jsp页面。

 

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

例子:

<result name="success" type="dispatcher">

              <param name="location">register-result.jsp</param>

       </result>

也可以简单写成这样:

   <result name="success" type="dispatcher">register-result.jsp</result>

 

Redirect将响应重定向到浏览器指定的位置,它将会导致Action执行完成的数据丢失或不再可用。它在程序里是通过调用javax.servlet.http.HttpServletResponse.sendRedirect(String location)方法,将响应定向到参数location指定的、新的url中。

 

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

例子

       <result name="success" type="redirect">

           <param name="location">foo.jsp</param>

           <param name="parse">false</param>

</result>

 

Action Chaining一种特殊的视图结果,将Action执行完之后链接到另一个Action中继续执行。新的Action使用上一个Action的上下文(ActionContext)。

 

参数(Parameters)

是否必需

 

actionName

将要被链接的Action名字

namespace

被链接的Action的命名空间(namespace),如果不设置,默认的即是当前的命名空间

例子:

<result name="success" type="chain">

    <param name="actionName">bar</param>

    <param name="namespace">/foo</param>

</result>

 

将要调用的Action如下:

<action name="bar" class="myPackage.barAction">

    ...

</action>

 

Velocity它类似Jsp的执行环境(使用JavaServlet容器),将Velocity模板转化成数据流的形式,直接通过JavaServlet输出。

 

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置(一般是.vm页面)

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

例子:

<result name="success" type="velocity">

    <param name="location">foo.vm</param>

</result>

 

FreeMarkerFreeMarker是一个纯Java模板引擎;一个普通的基于模板生成文本的工具,它只能应用在Web应用环境中。

 

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

contentType

如果不指定,默认的是"text/html"

例子:

<result name="success" type="freemarker">foo.ftl</result>

 

JasperReportsAction执行的结果通过JasperReports报表形式输出,可以指定JasperReports支持的输出格式(PDFHTMLXLSCSVXML等),默认是通过PDF格式输出。

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

dataSource

它是Action的一个字段(通常是一个List),OGNL表达式被用来去value stackOgnlValueStack)重新找回这个dataSource

format

报表生成的数据格式,默认的是pdf

例子:

<result name="success" type="jasper">

    <param name="location">foo.jasper</param>

    <param name="dataSource">mySource</param>

    <param name="format">CSV</param>

</result>

 

或者默认的pdf格式

<result name="success" type="jasper">

    <param name="location">foo.jasper</param>

    <param name="dataSource">mySource</param>

</result>

 

XML/XSL将结果转换为xml输出

 

参数(Parameters)

是否必需

 

location

执行完成之后转向的位置

parse

默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析

例子:

<result name="success" type="xslt">foo.xslt</result>               

 

 

 

 

 


 

表达式与言ELOGNL

OGNL介绍

OGNLObject-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

XWork遵循“不要重复地发明同一个轮子”的理论,它的表达式语言核心用的就是这个OGNL。我们先来看看一个简单的例子:

还记得我们用户注册的那个例子吗?我们输入框的name用到的名字就是OGNL的表达式,比如:用户名的输入框:“<input type="text" name="user.username">”,在用户注册成功之后我们要显示用户注册的信息,用了“<ww:property value="user.username"/>”。Input输入框里的“user.username”,它解析成Java语句为:getUser().setUsername();property标签里的“user.username”解析为Java语句:getUser.getUsername();

我们的两个表达式都是相同的,但前一个保存对象属性的值,后一个是取得对象属性的值。表达式语言简单、易懂却又功能强大,关于OGNL更多的介绍可以去http://www.,那里有很详细的文档。

值堆栈-OgnlValueStack

OGNL在框架中的应用,最主要是支持我们的值堆栈(Value Stack)——OgnlValueStack,它主要的功能是通过表达式语言来存取对象的属性。用户界面输入数据,它会根据保存表达式将数据依次保存到它堆栈的对象中,业务操作完成,结果数据会通过表达式被获取、输出。

还记得我们用户注册的例子吗?下面我们用一段程序来演示它向OgnlValueStack中保存、取得数据的步骤:

// DemoRegisterValueStack

package example.register;

 

import com.opensymphony.xwork.util.OgnlValueStack;

 

/**

 * @author moxie-qac

 *         achqian@yahoo.com.cn

 *

 */

public class DemoRegisterValueStack {

    public void demo(){

       RegisterAction action = new RegisterAction();

       OgnlValueStack valueStack= new OgnlValueStack();

       valueStack.push(action);

      

       valueStack.setValue("user.username","Moxie");

        System.out.println("username = "+valueStack.findValue("user.username"));

    }

 

    public static void main(String[] args) {

DemoRegisterValueStack demoValueStack = new DemoRegisterValueStack();

       demoValueStack.demo();

    }

}

我们来看一看它的demo()方法:

1、 创建我们的ActionRegisterAction)类的对象action,将action对象压入堆栈valueStack中。在WebWrokAction的创建、入栈是在DefaultActionInvocation构造函数中进行的,详细介绍见:ServletDispatcher原理。

2、 通过表达式语言,调用堆栈对象的get()set()方法,设置该对象的值。

   public void setValue(String expr, Object value)

   语句:valueStack.setValue("user.username","Moxie");

   的作用等同于:action.getUser().setUsername("Moxie");

3、 通过表达式语言,去堆栈对象中查找我们前面保存的值,并在控制台打印。valueStack.findValue("user.username")等同与语句
action.getUser().getUsername()

最后控制台打印的结果:

       username = Moxie

CompoundRoot

OgnlValueStack中,一个堆栈其实是一个List。查看OgnlValueStack你会发现,堆栈就是com.opensymphony.xwork.util.CompoundRoot类的对象:

public class CompoundRoot extends ArrayList {

    //~ Constructors /////////////////////////////////////

    public CompoundRoot() {

    }

    public CompoundRoot(List list) {

        super(list);

    }

    //~ Methods ////////////////////////////////////////////

    public CompoundRoot cutStack(int index) {

        return new CompoundRoot(subList(index, size()));

    }

    public Object peek() {

        return get(0);

    }

    public Object pop() {

        return remove(0);

    }

    public void push(Object o) {

        add(0, o);

    }

}

我们通过表达式向堆栈对象操作时,我们并不知道堆栈中有哪些对象。OgnlValueStack会根据堆栈由上向下的顺序(先入栈在下面,最后入栈在最上面)依次去查找与表达式匹配的对象方法,找到即进行相应的存取操作。假设后面对象也有相同的方法,将不会被调用。

下面我们看一个对OgnlValueStack操作的程序,它主要演示了如何对Map对象的存取和OgnlValueStack堆栈的原理:

/*

 * Created on 2004-6-15

 * DemoGroupValueStack.java

 */

package example.register;

 

import com.opensymphony.xwork.util.OgnlValueStack;

 

/**

 * @author moxie-qac

 *         achqian@yahoo.com.cn

 *

 */

public class DemoGroupValueStack {

   

    public void demoAction(){

       DemoGroupAction action = new DemoGroupAction();

       OgnlValueStack valueStack= new OgnlValueStack();

       valueStack.push(action);

      

       User zhao = new User();

       zhao.setUsername("zhao");

       zhao.setEmail("zhao@yahoo.com.cn");

      

       User qian = new User();

       qian.setUsername("qian");

       qian.setEmail("qian@yahoo.com.cn");

      

       valueStack.setValue("users[‘zhao‘]",zhao);

       valueStack.setValue("users[‘qian‘]",qian);

      

      

       System.out.println("users[‘zhao‘] = "+valueStack.findValue("users[‘zhao‘]"));

       System.out.println("users[‘qian‘] = "+valueStack.findValue("users[‘qian‘]"));

       System.out.println("users size = "+valueStack.findValue("users.size"));

      

       System.out.println("allUserName[0] = "+valueStack.findValue("allUserName[0]"));

    }

   

    public void demoModels(){

      

       User model_a = new User();

       model_a.setUsername("model_a");

       User model_b = new User();

       model_b.setUsername("model_b");

       User model_c = new User();

       model_c.setUsername("model_c");

      

       OgnlValueStack valueStack= new OgnlValueStack();

       valueStack.push(model_a);

       valueStack.push(model_b);

       valueStack.push(model_c);

      

       System.out.println("username = "+valueStack.findValue("username"));

       System.out.println("[1].username = "+valueStack.findValue("[1].username"));

       System.out.println("[0].toString = "+valueStack.findValue("[0]"));

       System.out.println("[1].toString = "+valueStack.findValue("[1]"));

       System.out.println("[2].toString = "+valueStack.findValue("[2]"));

      

    }

    public static void main(String[] args) {

       DemoGroupValueStack demoValueStack = new DemoGroupValueStack();

       demoValueStack.demoAction();

       demoValueStack.demoModels();

    }

}

 

/*

 * Created on 2004-6-15

 * DemoAction.java

 */

package example.register;

 

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

/**

 * @author moxie-qac

 *        achqian@yahoo.com.cn

 *

 */

public class DemoGroupAction {

   

    private Map users = new HashMap();

   

    public Map getUsers(){

       return this.users;

    }

   

    public List getAllUserName(){

       return new ArrayList(users.keySet());

    }

    public String execute(){

       //执行业务操作

       return null;

    }

    public String toString(){

       return users.toString();

    }

}

注意:1Map属性的存取,它的表达式语言如:users[‘zhao‘],注意它用’’来引用HashMapkey字符串。

2demoModels()方法演示了OgnlValueStack中堆栈的原理,请特别注意它的[0].toString[1].toString[2].toString,它们依次调用堆栈中对象的toString()方法,并逐一的减少堆栈最上面的对象。

控制台输出的结果如下:

users[‘zhao‘] = username=zhao;password=null;email=zhao@yahoo.com.cn;age=0

users[‘qian‘] = username=qian;password=null;email=qian@yahoo.com.cn;age=0

users size = 2

allUserName[0] = qian

 

username = model_c

[1].username = model_b

[0].toString = [username=model_c;password=null;email=null;age=0, username=model_b;password=null;email=null;age=0, username=model_a;password=null;email=null;age=0]

[1].toString = [username=model_b;password=null;email=null;age=0, username=model_a;password=null;email=null;age=0]

[2].toString = [username=model_a;password=null;email=null;age=0]


 

Interceptor(拦截器)框架

Interceptor(拦截器)将Action共用的行为独立出来,在Action执行前后运行。这也就是我们所说的AOPAspect Oriented Programming,面向切面编程),它是分散关注的编程方法,它将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。

Interceptor将很多功能从我们的Action中独立出来,大量减少了我们Action的代码,独立出来的行为具有很好的重用性。XWorkWebWork的许多功能都是有Interceptor实现,可以在配置文件中组装Action用到的Interceptor,它会按照你指定的顺序,在Action执行前后运行。Interceptor在框架中的应用如下图所示:

 

 

当你提交对Aciton(默认是.action结尾的Url)的请求时,ServletDispatcher会根据你的请求,去调度并执行相应的Action。在Action执行之前,调用被 Interceptor截取,InterceptorAction执行前后运行。

我们在用户注册的例子中就使用了取得Request请求参数的拦截器,配置文件中<interceptor-ref name="params"/>将拦截器params组装到RegisterAction中。“params”在我们的webwork-default.xml配置文件中有定义,webwork-default.xml中拦截器的定义如下:

<interceptors>

            <interceptor name="timer" class="com.opensymphony.xwork.interceptor.TimerInterceptor"/>

            <interceptor name="logger" class="com.opensymphony.xwork.interceptor.LoggingInterceptor"/>

            <interceptor name="chain" class="com.opensymphony.xwork.interceptor.ChainingInterceptor"/>

            <interceptor name="static-params" class="com.opensymphony.xwork.interceptor.StaticParametersInterceptor"/>

            <interceptor name="params" class="com.opensymphony.xwork.interceptor.ParametersInterceptor"/>

            <interceptor name="model-driven" class="com.opensymphony.xwork.interceptor.ModelDrivenInterceptor"/>

            <interceptor name="component" class="com.opensymphony.xwork.interceptor.component.ComponentInterceptor"/>

            <interceptor name="token" class="com.opensymphony.webwork.interceptor.TokenInterceptor"/>

            <interceptor name="token-session" class="com.opensymphony.webwork.interceptor.TokenSessionStoreInterceptor"/>

            <interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>

            <interceptor name="workflow" class="com.opensymphony.xwork.interceptor.DefaultWorkflowInterceptor"/>

            <interceptor name="servlet-config" class="com.opensymphony.webwork.interceptor.ServletConfigInterceptor"/>

            <interceptor name="prepare" class="com.opensymphony.xwork.interceptor.PrepareInterceptor"/>

            <interceptor name="conversionError" class="com.opensymphony.webwork.interceptor.WebWorkConversionErrorInterceptor"/>

            <interceptor-stack name="defaultStack">

                <interceptor-ref name="static-params"/>

                <interceptor-ref name="params"/>

                <interceptor-ref name="conversionError"/>

            </interceptor-stack>

            <interceptor-stack name="validationWorkflowStack">

                <interceptor-ref name="defaultStack"/>

                <interceptor-ref name="validation"/>

                <interceptor-ref name="workflow"/>

            </interceptor-stack>

        </interceptors>

这些都时有框架提供的默认的Interceptor,下面我来看看Interceptor使用的步骤:

1、 创建一个自己需要的Interceptor类,它必需实现
com.opensymphony.xwork.interceptor.Interceptor
接口,具体的开发见下面的Interceptor的原理。

2、 在配置文件(xwork..xml)中申明这个Interceptor类,它放在标签<interceptor />中,同是<interceptor />标签嵌入在<interceptors />标签内部。

3、 创建Interceptor栈,使用标签:<interceptor-stack />让一组Interceptor可以按次序调用。(可选)

4、 指定Action所要用到的Interceptor(前面申明过的),可以用<interceptor-ref /><default-interceptor-ref />标签。前面的标签指定某个Action所用到的Interceptor,如果Action没有被用<interceptor-ref />指定Interceptor,它将使用<default-interceptor-ref />指定的Interceptor

框架中给我们提供了很多实用的Interceptor,它的定义上面已经给出,它的具体功能如下:

l         timer:记录Action执行的时间,并做为日志信息输出;

l         logger:在日志信息中输出要执行的Action信息;

l         chain:将前一个执行结束的Action属性设置到当前的Action中。它被用在ResultType为“chain”指定结果的Action中,该结果Action对象会从OgnlValueStack中获得前一个Action对应的属性,它实现Action链之间的数据传递;

l         static-params:将xwork.xml配置文件里定义的Action参数,设置到对应的Action中。Action参数使用<param />标签,是<action />标签的直接子元素。我们这里定义的Action类必需实现com.opensymphony.xwork.config.entities. Parameterizable接口;

l         params:将Request请求的参数设置到相应Action对象的属性中,用户注册例子用到过这个拦截器;

l         model-driven:如果Action实现ModelDriven接口,它将getModel()取得的模型对象存入OgnlValueStack中;

l         component:激活组件功能支持,让注册过的组件在当前Action中可用,即为Action提供IoC(依赖倒转控制)框架的支持;

l         token:核对当前Action请求(request)的有效标识,防止重复提交Action请求(request)

l         token-session:功能同上,但是当提交无效的Action请求标识时,它会将请求数据保存到session中。

l         validation:实现使用xml配置文件({Action}-validation.xml)对Action属性值进行验证,详细请看后面介绍的验证框架。

l         workflow:调用Action类的验证功能,假设Action使用ValidationAware实现验证(ActionSupport提供此功能),如果验证没有通过,workflow会将请求返回到input视图(Action<result />中定义的)。

l         servlet-config:提供Action直接对HttpServletRequestHttpServletResponseJavaServlet api的访问,Action要实现相应的接口,例如:ServletRequestAwareServletResponseAware。如果必需要提供对JavaServlet api的访问,我们建议使用ServletActionContext,在前面ActionContext章节中有介绍。

l         prepare:在Action执行之前调用Actionprepare()方法,这个方法是用来准备Action执行之前要做的工作。它要求我们的Action必需实现com.opensymphony.xwork. Preparable接口

l         conversionError:用来处理框架进行类型转化(Type Conversion)时的出错信息。它将存储在ActionContext中的类型转化(Type Conversion)错误信息转化成相应的Action字段的错误信息,保存在堆栈中。根据需要,可以将这些错误信息在视图中显示出来。

Interceptor的原理

下面我们来看看Interceptor是如何实现在Action执行前后调用的:

ActionInterceptor在框架中的执行,是由ActionInvocation对象调用的。它是用方法:String invoke() throws Exception;来实现的,它首先会依次调用Action对应的Interceptor,执行完成所有的Interceptor之后,再去调用Action的方法,代码如下:

if (interceptors.hasNext()) {

Interceptor interceptor = (Interceptor) interceptors.next();

resultCode = interceptor.intercept(this);

} else {

     if (proxy.getConfig().getMethodName() == null) {

resultCode = getAction().execute();

} else {

        resultCode = invokeAction(getAction(), proxy.getConfig());

}

}

它会在拦截器栈中遍历Interceptor,调用Interceptor方法:
String intercept(ActionInvocation invocation) throws Exception;

我们一直都提到,Interceptor是在Action前后执行,可是从上面的代码我们看到的却是执行完所有Interceptorintercept()方法之后再去调用我们的Action。“在Action前后执行”是如何实现的呢?我们来看看抽象类AroundInterceptorintercept()实现:

public String intercept(ActionInvocation invocation) throws Exception {

        String result = null;

 

        before(invocation);

        result = invocation.invoke();

        after(invocation, result);

 

        return result;

    }

原来在intercept()方法又对ActionInvocationinvoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke();执行结束,即:Action执行完并返回结果result,这时Interceptor对象会按照刚开始执行的逆向顺序依次执行结束。这样before()方法将在Action执行前调用,after()方法在Action执行之后运行。

验证框架

WebWork提供了在Action执行之前,对输入数据的验证功能,它使用了其核心XWork的验证框架。提供了如下功能:

1、 可配置的验证文件。它的验证文件是一个独立的XML配置文件,对验证的添加、修改只需更改配置文件,无需编译任何的Class

2、 验证文件和被验证的对象完全解藕。验证对象是普通的JavaBean就可以了(可以是FormBean、域对象等),它们不需实现任何额外的方法或继承额外的类。

3、 多种不同的验证方式。因为它验证功能是可以继承的,所以可以用多种不同的方式指定验证文件,比如:通过父类的Action、通过Action、通过Action的方法、通过Action所使用的对象,等等。

4、 强大的表达式验证。它使用了OGNL的表达式语言,提供强大的表达式验证功能。

5、 同时支持服务器端和客户端验证。

为用户注册添加验证功能

下面我们来看看如何为用户注册添加验证功能:

1、 注册我们的验证类型

WebWork为不同的验证要求提供不同的验证类型。一个验证类型,一般是有一个类来提供。这个类必须实现接口:com.opensymphony.xwork.validator.Validator,但我们在写自己的验证类型时,无需直接实现Validator接口,它有抽象类可供直接使用如ValidatorSupportFieldValidatorSupport等。

验证类型在使用之前,必须要在ValidatorFactorycom.opensymphony.xwork.validator. ValidatorFactory)中注册。可以有二种方法实现验证类型的注册。一、写程序代码进行注册,它使用ValidatorFactory类的静态方法:registerValidator(String name, String className)二、使用配置文件validators.xml进行注册,要求把文件validators.xml放到ClassPath的跟目录中(/WEB-INF/classes)。但在实际开发中,一般都使用第二中注册方法。我们的验证类型注册如下:

<validators>

    <validator name="required" class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/>

    <validator name="requiredstring" class="com.opensymphony.xwork.validator.validators.RequiredStringValidator"/>

    <validator name="int" class="com.opensymphony.xwork.validator.validators.IntRangeFieldValidator"/>

    <validator name="date" class="com.opensymphony.xwork.validator.validators.DateRangeFieldValidator"/>

    <validator name="expression" class="com.opensymphony.xwork.validator.validators.ExpressionValidator"/>

    <validator name="fieldexpression" class="com.opensymphony.xwork.validator.validators.FieldExpressionValidator"/>

    <validator name="email" class="com.opensymphony.xwork.validator.validators.EmailValidator"/>

    <validator name="url" class="com.opensymphony.xwork.validator.validators.URLValidator"/>

    <validator name="visitor" class="com.opensymphony.xwork.validator.validators.VisitorFieldValidator"/>

    <validator name="conversion" class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/>

    <validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>

</validators>

注册验证类型的配置文件非常简单。它使用标签<validator>提供名-值对的形式注册。这样我们的验证文件就可以直接引用它的名字。

2、 开启Action的验证功能

 如果Action要使用验证框架的验证功能,它必须在配置文件中指定拦截器“validation”,它的定义如下:

<interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>

我们的验证文件必须以ActionName-validation.xml格式命名,它必须被放置到与这个Action相同的包中。你也可以为这个Action通过别名的方式指定验证文件,它的命名格式为:ActionName-aliasname-validation.xml。“ActionName ”是我们Action的类名;“aliasname”是我们在配置文件(xwork.xml)中定义这个Action所用到的名称。这样,同一个Action类,在配置文件中的不同定义就可以对应不同的验证文件。验证框架也会根据Action的继承结构去查找Action的父类验证文件,如果找到它会去执行这个父类的验证。

 

3、 实现我们的验证文件:RegisterActionSupport-validation.xml

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www./xwork/xwork-validator-1.0.dtd">

<validators>

    <field name="user.username">

    <field-validator type="requiredstring">

            <message>You must enter a value for username.</message>

        </field-validator>

    </field>

    <field name="user.password">

    <field-validator type="requiredstring">

            <message>You must enter a value for password.</message>

        </field-validator>

        <field-validator type="fieldexpression">

            <param name="expression">user.password == verifyPassword</param>

            <message>Passwords don‘t match.</message>

        </field-validator>

    </field>

    <field name="user.email">

    <field-validator type="email">

            <message>You must enter a valid email.</message>

        </field-validator>

    </field>

    <field name="user.age">

    <field-validator type="int">

            <param name="min">6</param>

            <param name="max">100</param>

            <message>Age must be between ${min} and ${max}, current value is ${user.age}.</message>

        </field-validator>

    </field>

</validators>

说明:

1)、<field>标签代表一个字段,它的属性“name”和页面输入框的“name”属性必需完全一致,其实它也就是我们的表达式语言。

2)、<field-validator>标签定义我们的验证规则,type属性的值就是就是我们前面定义的验证类型。

3)、验证文件中,字段的数据是通过表达式语言从我们的值堆栈(OgnlValueStack)中取得,一般是ActionModel对象。例如:我们的字段“user.age”,它会通过ActiongetUser().getAge()来取得用户输入的年龄,再来根据验证的类型“int”和最大值最小值的参数来判断输入的数据是否能通过验证。

4)、不管验证是否通过,我们的Action都会执行,但如果验证没有通过,它不会调用Actionexecute()方法。

 

4、 显示Action的验证错误信息

如果用户输入的数据验证没有通过,我们需重新返回输入页面,并给出错误信息提示。拦截器栈“validationWorkflowStack”为我们实现了这个功能。它首先验证用户输入的数据,如果验证没有通过将不执行我们Actionexecute()方法,而是将请求重新返回到输入页面。

我们的xwork.xml配置文件如下:

<action name="registerSupport" class="example.register.RegisterActionSupport" >

           <result name="success" type="dispatcher">

              <param name="location">/register-result.jsp</param>

           </result>

           <result name="input" type="dispatcher">

              <param name="location">/registerSupport.jsp</param>

           </result>

           <interceptor-ref name="validationWorkflowStack"/>

       </action>

 

通过接口ValidationAware我们可以获得类级别或字段级别的验证错误信息,这个错误信息也就是我们验证文件中<message>标签里的数据。ActionSupport类已实现了此接口,这样在应用中我们的Action只要继承ActionSupport类就可以了。RegisterActionSupport.java代码如下:

package example.register;

 

import com.opensymphony.xwork.ActionSupport;

 

/**

 * @author moxie-qac

 *         achqian@yahoo.com.cn

 *

 */

public class RegisterActionSupport extends ActionSupport {

 

    private User user= new User();

    private String verifyPassword;

   

    public User getUser(){

       return this.user;

    }

   

    public String execute(){

       //在这里调用用户注册的业务逻辑,比如:将注册信息存储到数据库

       return SUCCESS;

    }

 

    public String getVerifyPassword(){

       return this.verifyPassword;

    }

   

    public void setVerifyPassword(String verPassword){

       this.verifyPassword = verPassword;

    }

}

我们WebWorkUI标签库直接提供了验证错误信息显示功能。如果字段级别的验证没有通过,它会在输入框上方显示验证文件定义的错误提示信息。我们将用户输入的页面更改如下:

registerSupport.jsp

<%@ taglib uri="webwork" prefix="ww" %>

<html>

<head><title>Register Example</title></head>

<body>

<table border=0 width=97%>

<tr><td align="left">

    <ww:form name="‘test‘" action="‘/example/registerSupport.action‘" method="‘POST‘">

            <ww:textfield label="‘Username‘" name="‘user.username‘" required="true"/>

            <ww:textfield label="‘Password‘" name="‘user.password‘" required="true"/>

            <ww:textfield label="‘VerifyPassword‘" name="‘verifyPassword‘" required="true"/>

            <ww:textfield label="‘Email‘" name="‘user.email‘" required="true"/>

            <ww:textfield label="‘Age‘" name="‘user.age‘" required="true"/>

            <ww:submit value="‘Submit‘"/>

         </ww:form>

</td></tr>

</table>

</body>

</html>

我们上面的例子使用的是服务器端验证。WebWork也为我们提供了方便的客户端验证。它将验证自动生成JavaScript脚本。如果要使用客户端验证只需改变相应的验证类型就可以了(输入页面的表单必需使用<ww:form>标签,并设置属性“validate="true"”)。具体的验证类型可以在WebWork的包com.opensymphony.webwork.validators中找到。


 

XWork配置详述

XWork配置文件是以“xwork”命名的.xml文件,它必需放到类路径(classPath)的根目录, Web应用一般放在classes目录中,它需要遵守DTD的规范(现在是xwork-1.0.dtd)。这个文件定义了我们的ActionInterceptorResult的配置和相互之间的映射。下面我们看看用户注册的完整XWork配置文件:

<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www./xwork/xwork-1.0.dtd">

 

<xwork>

 

    <include file="webwork-default.xml"/>

   

    <package name="example" extends="webwork-default">

       <action name="register" class="example.register.RegisterAction" >

           <result name="success" type="dispatcher">

              <param name="location">/register-result.jsp</param>

           </result>

           <interceptor-ref name="params"/>

       </action>

      

       <action name="registersupport" class="example.register.RegisterActionSupport" >

           <result name="success" type="dispatcher">

              <param name="location">/register-result.jsp</param>

           </result>

           <result name="input" type="dispatcher">

              <param name="location">/registerSupport.jsp</param>

           </result>

           <interceptor-ref name="validationWorkflowStack"/>

       </action>

      

    </package>

 

</xwork>

xwork.xml文件的标签元素

Xworkxwork配置文件的所有内容,都是定义在<xwork>标签中,它的直接子标签有<package><include>

Package我们的ActionInterceptorResult都是在此标签中定义。<package>标签有一个必需的属性“name”,它用来标识唯一的一个package。属性“extends”是可选的,它用来继承前面定义的一个或一个以上package配置信息,包括所有的interceptorinterceptor-stackaction的配置信息。注意,配置文件按文档的顺序,由上向下执行,因此,用“extends”引用的package必需在引用之前定义。属性“sbstract”是可选的,它用来设置package为抽象的package,它可以被继承同时它的Action配置信息在运行时将不可见。

属性namespace也是可选的,它用来分隔不同package定义的action,让这些action处于不同的命名空间(namespaces)。这样,我们不同的package可以有相同的action命名,因为可以通过命名空间来区分。如果不指定namespace,默认的是空字符串。命名空间也可以被用在安全控制方面,它可以根据不同的命名空间指定不同的访问权限。

是否必需

  

name

用来标识package的名称

extends

继承它所扩展的package配置信息

namespace

指定package的命名空间,默认是””

abstract

声明package是抽象的

 

Result-type用来定义输出结果类型的Class,它用简单的名-值对来定义。当然,我们自己写的输出结果类型也必需在这里定义。例如:

<result-type name="dispatcher" class="com.opensymphony.webwork.dispatcher.ServletDispatcherResult" default="true"/>default="true"表示如果在Actionresult中不指定result-type,就使用这个默认的result-type

 

Interceptors它是一个简单的<interceptors>  <interceptors/>标签,我们的interceptorinterceptor-stack都在此标签内定义。

 

Interceptor当然,就是用来定义我们的拦截器。它的定义非常简单,名-值对的形式。例如:<interceptor name="timer" class="com.opensymphony.xwork.interceptor.TimerInterceptor"/>。在action中,可以通过<interceptor-ref />来直接引用前面定义了的拦截器。

 

Interceptor-stack用来将上面定义的interceptor组织成堆栈的形式,这样我们就可以创建一组标准的interceptor,让它按照顺序执行。在我们的Action中直接引用这个interceptor堆栈就可以了,不用逐个interceptor去引用。

例如:

<interceptor-stack name="validationWorkflowStack">

               <interceptor-ref name="defaultStack"/>

                <interceptor-ref name="validation"/>

                <interceptor-ref name="workflow"/>

            </interceptor-stack>

Interceptor Param:我们的interceptor是在ActionConfig级别被实例化和存储的,也就是说一个Action引用的每个interceptor都会有相应的实例。这样,我们在定义和引用interceptor的时候都可以为它设置相应的参数值。例如:

<interceptor name="test" class="com.opensymphony.xwork.TestInterceptor">

    <param name="foo">expectedFoo</param>

</interceptor>

ActionInterceptor-stack中引用时也可以设置参数,例如:

<interceptor-ref name="test">

    <param name="expectedFoo">expectedFoo</param>

</interceptor-ref>

注意:在Action引用的时候,如果引用的是Interceptor-stack,则不允许设置参数,否则会报错。

 

Global-results它允许我们定义全局的输出结果(global result),比如登陆页面、操作错误处理页面。只要继承它所在的package,这些输出结果都是可见的。

例如:

<global-results>

           <result name="login" type="dispatcher">

              <param name="location">/login.jsp</param>

           </result>

           <result name="error" type="dispatcher">

              <param name="location">/error.jsp</param>

           </result>

       </global-results>

如果我们的Action执行完返回“login”,它将调用上面的这个输出结果,将输出派遣到根目录下的login.jsp页面。

 

Action用来配置Action的名称(name)和它对应的Class。我们将通过这个Action的名称和它所在packagenamespace去配置文件中取得这个Action的配置信息。它可以通过<param>来设置参数,Action在执行的时候会取得配置文件里设置的参数(通过拦截器StaticParametersInterceptor)。

Action可以配置一个或多个输出结果(result)。一个输出结果的名称,对应于Action执行完成返回的字符串。<result>标签的type属性,对应我们前面定义过的result-type,说明reslut的类型。例如:

<action name="register" class="example.register.RegisterAction" >

           <result name="success" type="dispatcher">

              <param name="location">/register-result.jsp</param>

           </result>

           <interceptor-ref name="params"/>

       </action>

当然,我们的Action用到的拦截器也是在这里配置的,通过<interceptor-ref>标签,属性“name”的值,对应前面定义的interceptorinterceptor-stack如果Action中没有用<interceptor-ref>标签指定拦截器,它将使用默认的<default-interceptor-ref>标签定义的拦截器。

 

Includexwork..xml文件可以被分成好几个不同的文件,xwork..xml通过<include>标签引用被包含的文件,例如:<include file="webwork-default.xml"/>。被包含的文件必需是package标签里的内容,我们看看<include>标签在配置文件里的位置就知道了。如果要继承被包含文件的package,我们必需将<include>标签放在其上面,因为配置文件是按照由上而下的顺序解析的。


 

实战G-Roller-WW

G-Roller-WW介绍

JSTLWebWork的整合

中文解决方案

 

WebWork与其它开源项目的集成

Spring

Hibernate

Xml-RPC


 

总结

WebWork功能非常强大,除了上面介绍的以外,它还有很好的国际化支持功能,IoC(Inversion of control,依赖倒装控制)框架支持;同时,它也可以很好的与其它的开源项目集成,如:SitemeshSpringPicoHibernateJUnitQuartz等。

 “最好的文档就是代码”,WebWork代码可读性非常好,特别是2.1版本加了很多详尽的注释,在此向读者强烈推荐,如果想更深入了解WebWork,建议多看它的代码文档。

到此,您已经了解了WebWork的所有特性。它确实是一个非常优秀的开源J2EE Web框架,同时我并不否定其它的框架,比如StrutsTapestryMaverick等,既然存在,它就一定有着自身存在价值和理由。

这么多的Web框架,有很多朋友在面临选择的时候也许会非常矛盾,不知应该如何抉择。在这,我的建议:关于开源Web框架的选择,应该根据团队的整体技术能力和要实施的项目来共同决定。关于是否要在项目中使用WebWork,假如你们已经在团队中使用类似Struts这样的J2EE框架,开发人员都已熟悉并有了很多技术和项目经验的积累,那我建议你们暂时不要去使用WebWork,但我强烈建议找一个有代表性的模块,将他们尝试用WebWork改写,我想,下个项目,也许你们就会改变注意,考虑使用WebWork。但,如果你们正在为具体选择哪种Web框架而发愁,我相信WebWork一定是您最佳的选择。

WebWork功能很多,但我们要牢记它的宗旨:致力于组件化和代码重用,同时简单、灵活,更好的提高生产率。

                                                 

附录

我钟爱的Opensympnony

我是从WebWork开始认识Opensymphonyhttp://www.)的,它是一个很好提供开源项目的组织。同Jakarta相比,这里的组件(Component)更多的是精致小巧的设计,它们尤以简单易用和可插拔的灵活性见长。除了我们这里介绍的WebWorkXwork,下面我们将简单介绍其他的一些组件:

 

OSWorkFlow工作流引擎。它的流程定义灵活清晰,工作流引擎支持多种持久方式(MemoryStore ,SerializableStore, JDBCStore, OfbizStore, and EJBStore,HibernateStore等),具有极强的可扩展性。它提供了强大的脚本支持(BeanShellBSF等),多样化的function,function可以直接使用普通java类函数、XworkActionJMSEJB、脚本等。它还提供了一个基于JGraph的流程设计器。

   最新版本OSWorkFlow2.7也添加了对Spring框架的支持,流程设计器也有了很好的改进。

 

Quartz它是一个实现任务定时调度的框架,原先是一个独立的project,后来并入OpenSymphony。它是一个非常轻量级的,并具有高度的可升级性,提供了简单易用的接口。它提供了强大的任务调度运行方式,可以独立运行、可以作为EJB部署于容器中、本身支持cluster,等等。

  最新的版本是1.4,在性能和功能上都又有了很好的提高。

 

SiteMesh它主要用来对Web页面的布局管理,并且致力为很多页面组成的大型网站提供提供统一的风格、导航和布局功能。

它通过filter截取requestresponse,并给原始的页面加入一定的装饰(Decorator(可能为header,footer...),然后把结果返回给客户端,并且被装饰的原始页面并不知道SiteMesh的装饰,这也就达到了解耦的目的。

 

OSCacheJ2EE Caching机制。它主要用于JSP CachingRequest CachingGeneral-Purpose Cache三个方面。在JSP CachingRequest Caching方面,OSCache能够解决动态网站的基本问题:缓存动态内容、缓存二进制内容、错误包容。在General-Purpose Cache方面,在Java应用中通过调用OSCacheAPI来缓存任意的Java对象,hibernate 2.0开始对其也有支持。

OSCache标记库是一种开创性的JSP定制标记应用,提供了在现有JSP页面之内实现快速内存缓冲的功能。虽然已经有一些供应商在提供各种形式的缓存产品,但是,它们都属于面向特定供应商的产品。OSCache能够在任何JSP 1.2兼容的服务器上运行,它不仅能够为所有用户缓冲现有JSP代码块,而且能够以用户为单位进行缓冲。OSCache还包含一些提高可伸缩性的高级特性,比如:缓冲到磁盘,可编程的缓冲刷新,异常控制,等等。

 

OSCore是一组公共工具类,提供了丰富的常用方法的标准实现,供其他OpenSymphony组件使用。

 

PropertySet管理属性(Property)的好工具,它提供一个抽象方法来向一个持久性存储源中动态保存和取回类型化的属性数据。支持多种持久化方式,例如:XML, EJB, Ofbiz, JDBC, Castor JDO,Memory等,同时也提供了一个简单的API来根据你的需要写你自己定制的PropertySets

 

Clickstream它是一个JavaServlet过滤器,用来跟踪用户请求(比如:点击)和请求队列(比如:点击流)以向网络管理员显示谁在她的网站上以及每个用户正在访问那个页面。


 

从技术的角度Struts1.1WebWork2的比较

Struts1.1

WebWork2

Action

Struts里面,每一个Action类必需要继承一个抽象的类org.apache.struts.action.Action。这个在Java编程中会引来一些问题,就是关于多种继承的问题。

WebWorkAction类仅需要实现接口com.opensymphony.xwork.Action,也可以实现其它的接口来实现更多的功能,譬如:validate(验证),localware(国际化)等。当然,它也提供了一个类ActionSupport集成了上面的所有功能,我们在开发中可以根据需要选择。

线程模型

Struts Action必需是threadsafe方式,它仅仅允许一个实例去处理所有的请求。所以action用到的所有的资源都必需统一同步,这个就引起了线程安全的问题。

WebWork中,每个请求对应一个Action,因此没有线程的安全问题。实际上Servlet容器对每个请求也产生多个对象,它也没有证明对性能和垃圾回收产生太多的影响。

Servlet的依赖

Struts处理Action时必需要依赖ServletRequest ServletResponse,所有它摆脱不了Servlet容器。

WebWorkAction不用依赖Web层和其它的容器。它可以通过ActionContext,直接去访问RequestResponse,但这个是可选的,只有在必需的请求下使用。

测试

Struts的每个Action都同Web层耦合在一起,这样它的测试依赖于Web容器,单元测试也很难实现。不过有一个Junit的扩展工具Struts TestCase可以实现它的单元测试。

Webworkaction能够通过赋予一定的属性,就可以执行单元测试。同时也可以使用一个mock的实例去测试,而不是通过启动web容器来进行测试。

FormBean

Struts要求有FormBean对应每一个表单,而且FormBean必需继承抽象类ActionForm。而使用DynaBeans实际上没有太大的意义。不能够很好的处理现有的模型。

Webwork 能够动态的收集web的数据然后再赋值给bean。它也可以使用FormBean的形式,FormBean可以是普通的DTO和域对象,它不用重新根据域对象来生成新的FormBean,也不需继承抽象类ActionForm

前端表达式语言

Struts集成了JSTL,所以它主要使用JSTL的表达式语言来获取数据。可是JSTL的表达式语言在Collection和索引属性方面处理显得很弱。

WebWork的表达式语言使用了功能强大的OGNL。它使用OGNL建立一个OgnlValueStack来搜索数据。Webwork前端也可以使用JSTL,但它同时支持:velocityfreemakerjspparerxml

类型的转换

StrutsFormBean把所有的数据都作为String类型,它可以使用工具Commons-Beanutils进行类型转化。但它的转化都是在Class级别,而且转化的类型是不可配置的。类型转化时的错误信息返回给用户也是非常困难的。

WebWork使用OGNL进行类型转化,提供了所有基本类型的转化功能。类型转化可以直接对一个Class进行(Class级别)转化,也可以对Class的字段进行类型转化。它使用拦截器可以很容易的将类型转化的错误信息返回给用户,而且错误信息可以对应到一个相应的字段。

Action 执行前和后的处理

Struts处理Action的时候是基于classhierarchies,很难在action处理前和后进行操作。

Webwork2 允许您处理Action可以通过拦截器,就是在每一个Action处理前或者后进行其它操作。它的拦截器可以在配置文件中动态添加,这样Action和拦截器之间完全解藕,更好的实现了组件化。

验证处理

Struts的验证是调用FormBeanvalidator()方法,其实就是对FormBean的验证。它一般使用框架Commons Validation进行数据验证处理。它使用了一个全局的配置文件validation.xml定义了FormBean的验证信息。StrutsFormBean属性都被认为是String类型,所以它在验证时也需要额外的类型转化。

WebWork使用Xwork的验证框架进行验证处理,它可以通过配置拦截器来激活。它可以为每个需要验证的Class指定一个xml验证文件,也可以为一个Class在不同的情况指定不同的xml验证文件。WebWork证可以给每个Action类指定对应的验证文件,也可以给Action的字段去指定验证文件。通过拦截器来组装Action和其验证文件,使它们之间完全解藕。

Action执行的控制

Struts创建一个Action,如果想控制它的执行顺序将会非常困难。甚至你要重新去写Servlet来实现你的这个功能需求。

在这个方面,WebWork的拦截器栈提供了强大的功能。Action的所有切面功能都有拦截器来实现(比如:取得request请求参数、验证处理等),这样你就可以用拦截器栈来组织拦截器的执行顺序。例如:你需要在使用request请求参数来设置Action属性之前,使用IoC框架设置Action的属性,反之已然。这时,你就可以为packageAction指定一个拦截器栈来实现。

 


 

WebWork的项目资源

下面是我在研究WebWork时,使用的资源和研究过的项目,希望能对你能有帮助。

1、 当然就是WebWork的官方网站:http://www./webwork/

里面有最新的WebWork文档和它的一个wiki

2、 一本好书:Java Open Source Programming : with XDoclet, JUnit, WebWork, Hibernate,里面有很好的WebWork教程。它附带的源代码可以去http://www.amazon.com/下载,里面的petsoar是一个非常好的WebWork开源项目。

3、 Confluencehttp://www./software/confluence)是专业的J2EE wiki,用于知识管理和项目组交流。它使用的架构是webwork2+Spring+HibernateConfluence虽是商业软件,不过对于 OpenSource的项目它全部免费提供。它的架构思想很值得我们去学习。

4、 OpenReports(http://www.)是一个开源的项目,基于Web的报表系统。它用到的技术有:WebWork 2.0Velocity Hibernate

参考资料

http://www./

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注意:<result name="success" type="dispatcher">

              <param name="location">register-result.jsp</param>

       </result>

/register-result.jsp

 

location可以是一个绝对的URL,如http://www./也可以使用相对的URL。如果location以“/”开头,则容器认为相对于当前Web应用的根,否则,容器将解析为相对于当前请求的URL。这种重定向结果,将导致客户端浏览器的请求URL跳转。从浏览器中的地址栏中可以看到新的URL地址。

 

location可以是一个绝对的URL,如response.sendRedirect("http://java.")也可以使用相对的URL

如果location以“/”开头,则容器认为相对于当前Web应用的根,否则,容器将解析为相对于当前请求的URL

这种重定向的方法,将导致客户端浏览器的请求URL跳转。从浏览器中的地址栏中可以看到新的URL地址,

作用类似于上面设置HTTP响应头信息的实现。

 

在客户端浏览器地址栏中不会显示出转向后的地址

 

RequestDispatcher是一个Web资源的包装器,可以用来把当前request传递到该资源,

或者把新的资源包括到当前响应中。

forward()方法将当前的requestresponse重定向到该RequestDispacher指定的资源。

include()方法将把Request Dispatcher资源的输出包含到当前输出中。

 

RequestDispatcher.forward()方法和HttpServletResponse.sendRedirect()方法的区别是:

前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;

后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。

这样,从浏览器的地址栏中可以看到跳转后的链接地址。

 

注意:所有代码程序的重新检查。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多