webwork2.0配置详解
首先下载WebWork2 的最新版本(http://www.opensymphony.com/webwork/)。
这里我们所谈及的WebWork,实际上是Webwork+XWork的总集,Webwork1.x 版本中,
整个框架采用了紧耦合的设计(类似Struts),而2.0 之后,Webwork被拆分为两个部分,
即Webwork 2.x +XWork 1.x,设计上的改良带来了系统灵活性上的极大提升。
本例的部署如下图所示
index.jsp
<body>
<form action="/login.action">
<p align="center">
登录<br> </p>
用户名:<input type="text" name="model.username" /><br>
密 码 :<input type="password" name="model.password" /><br>
<p align="center"><input type="submit" value="提交" name="B1"/><input type="reset" value="重置" name="B2"/></p>
</form>
</body>
</html>
这里的index.jsp实际上是由纯html 组成,非常简单,其中包含一个表单:
<form action="/login.action">
这表明其提交对象为/login.action . 表单中同时包含两个文本输入框,
<input type="text" name="model.username" />
<input type="password" name="model.password" />
可以看到,两个输入框的名称均以“model”开头,这是因为在这里我们采用了WebWork
中Model-Driven的Action驱动模式
标准HTTP协议中并没有.action结尾的服务资源。我们需要在web.xml中加以设定:
……
<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结尾的服务请求将由ServletDispatcher 接管。
ServletDispatcher 接受到Servlet Container 传递过来的请求,将进行一下几个动作:
1. 从请求的服务名(/login.action)中解析出对应的Action名称(login)
2. 遍历 HttpServletRequest、HttpSession、ServletContext 中的数据,并将其复制到
Webwork的Map实现中,至此之后,所有数据操作均在此Map结构中进行,从而将内部结构与Servlet API相分离。
至此,Webwork 的工作阶段结束,数据将传递给XWork 进行下一步处理。从这里也可以看
到Webwork和xwork之间的切分点,Webwork为xwork提供了一个面向Servlet 的协议转换
器,将Servlet 相关的数据转构转换成xwork所需要的通用数据格式,而xwork将完成实际的
服务调度和功能实现。
这样一来,以xwork为核心,只需替换外围的协议转换组件,即可实现不同技术平台之间的
切换(如将面向Servlet的Webwork替换为面向JMS的协议转换器实现,即可在保留应用逻
辑实现的情况下,实现不同外部技术平台之间的移植)。
3. 以上述信息作为参数,调用ActionProxyFactory创建对应的ActionProxy实例。
ActionProxyFactory 将根据Xwork 配置文件(xwork.xml)中的设定,创建
ActionProxy实例,ActionProxy中包含了Action的配置信息(包括Action名称,
对应实现类等等)。
4. ActionProxy创建对应的Action实例,并根据配置进行一系列的处理程序。包括
执行相应的预处理程序(如通过Interceptor 将Map 中的请求数据转换为Action
所需要的Java 输入数据对象等),以及对Action 运行结果进行后处理。
ActionInvocation 是这一过程的调度者。而com.opensymphony.xwork.
DefaultActionInvocation 则是XWork 中对ActionInvocation 接口的标准实现,如
果有精力可以对此类进行仔细研读,掌握了这里面的玄机,相信XWork的引擎
就不再神秘。
下面我们来看配置文件:
xwork.xml:
<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<action name="login" class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
</result>
<interceptor-ref name="params" />
<interceptor-ref name="model-driven"/>
</action>
</package>
</xwork>
⑴ include
通过include 节点,我们可以将其他配置文件导入到默认配置文件xwork.xml 中。
从而实现良好的配置划分。
这里我们导入了Webwork 提供的默认配置webwork-default.xml(位于
webwork.jar 的根路径)。
⑵ package
XWork中,可以通过package对action进行分组。类似Java 中package和class的
关系。为可能出现的同名Action提供了命名空间上的隔离。
同时,package还支持继承关系。在这里的定义中,我们可以看到:
extends="webwork-default"
"webwork-default"是webwork-default.xml文件中定义的package,这里通
过继承,"default" package 自动拥有"webwork-default" package 中的所有
定义关系。
这个特性为我们的配置带来了极大便利。在实际开发过程中,我们可以根据自身
的应用特点,定义相应的package模板,并在各个项目中加以重用,无需再在重复
繁琐的配置过程中消耗精力和时间。
此外,我们还可以在Package节点中指定namespace,将我们的action分为若干个
逻辑区间。如:
<package name="default" namespace="/user"
extends="webwork-default">
就将此package中的action定义划归为/user 区间,之后在页面调用action的时候,
我们需要用/user/login.action 作为form action 的属性值。其中的/user/就指定了此
action的namespace,通过这样的机制,我们可以将系统内的action进行逻辑分类,
从而使得各模块之间的划分更加清晰。
⑶ action
Action配置节点,这里可以设定Action的名称和对应实现类。
⑷ result
通过result 节点,可以定义Action 返回语义,即根据返回值,决定处理模式以及
响应界面。
这里,返回值"success"(Action 调用返回值为String 类型)对应的处理模式为
"dispatcher"。
可选的处理模式还有:
1. dispatcher
本系统页面间转向。类似forward。
2. redirect
浏览器跳转。可转向其他系统页面。
3. chain
将处理结果转交给另外一个Action处理,以实现Action的链式处理。
4. velocity
将指定的velocity模板作为结果呈现界面。
5. xslt
将指定的XSLT 作为结果呈现界面。
随后的param节点则设定了相匹配的资源名称。
⑷ interceptor-ref
设定了施加于此Action的拦截器(interceptor)。关于拦截器,请参见稍后的“XWork
拦截器体系”部分。
interceptor-ref定义的是一个拦截器的应用,具体的拦截器设定,实际上是继
承于webwork-default package,我们可以在webwork-default.xml 中找到
对应的"params"和"model-driven"拦截器设置:
<interceptors>
……
<interceptor name="params"
class="com.opensymphony.xwork.interceptor.ParametersInt
erceptor" />
<interceptor name="model-driven"
class="com.opensymphony.xwork.interceptor.ModelDrivenIn
terceptor" />
……
</interceptors>
"params"大概是Webwork 中最重要、也最常用的一个Interceptor。上面曾经将
MVC工作流程划分为几个步骤,其中的第一步:
“将 Web 页面中的输入元素封装为一个(请求)数据对象”
就是通过"params"拦截器完成。Interceptor 将在Action 之前被调用,因而,
Interceptor 也成为将Webwork传来的MAP 格式的数据转换为强类型Java 对象的
最佳实现场所。
"model-driven"则是针对Action 的Model驱动模式的interceptor 实现。具体描
述请参见稍后的“Action驱动模式”部分
很可能我们的Action 都需要对这两个interceptor 进行引用。我们可以定义一个
interceptor-stack,将其作为一个interceptor 组合在所有Action 中引用。如,上面
的配置文件可修改为:
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<interceptors>
<interceptor-stack name="modelParamsStack">
<interceptor-ref name="params" />
<interceptor-ref name="model-driven" />
</interceptor-stack>
</interceptors>
<action name="login"
class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
<param name="location">/index.jsp</param>
</result>
<interceptor-ref name="modelParamsStack" />
</action>
</package>
</xwork>
通过引入interceptor-stack,我们可以减少interceptor 的重复申明。
下面是我们的Model对象:
LoginInfo.java:
public class LoginInfo {
private String password;
private String username;
private List messages = new ArrayList();
private String errorMessage;
public List getMessages() {
return messages;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
很简单,这只是一个纯粹的值对象(Value-Object)。这里,它扮演着模型(Model)的
角色,并与Action的输入输出密切相关。
与 SpringMVC中的Command对象不同,Webwork 中的Model对象,扮演着承上启下
的角色,它既是Action的输入参数,又包含了Action处理的结果数据。
换句话说,输入的Http请求参数,将被存储在Model对象传递给Action进行处理,Action
处理完毕之后,也将结果数据放置到Model 对象中,之后,Model 对象与返回界面融合生
成最后的反馈页面。
也正由于此,笔者建议在实际开发中采用Model-Driven 模式,而非Property-Driven 模
式(见稍后“Action驱动模式”部分),这将使得业务逻辑更加清晰可读。
对应的Action代码
public class LoginAction implements Action, ModelDriven {
private final static String LOGIN_FAIL="loginfail";
LoginInfo loginInfo = new LoginInfo();
public String execute() throws Exception {
if ("erica".equalsIgnoreCase(loginInfo.getUsername())
&& "mypass".equals(loginInfo.getPassword())) {
//将当前登录的用户名保存到Session
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
//出于演示目的,通过硬编码增加通知消息以供显示
loginInfo.getMessages().add("message1");
loginInfo.getMessages().add("message2");
loginInfo.getMessages().add("message3");
return SUCCESS;
}else{
loginInfo.setErrorMessage("Username/Password Error!");
return LOGIN_FAIL;
}
}
public Object getModel() {
return loginInfo;
}
}
1. Action
Action接口非常简单,它指定了Action的入口方法(execute),并定义了
几个默认的返回值常量:
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;
}
SUCCESS、NONE、ERROR、INPUT、LOGIN 几个字符串常量定义了常用的
几类返回值。我们可以在Action 实现中定义自己的返回类型,如本例中的
LOGIN_FAIL定义。
而execute方法,则是Action的入口方法,XWork将调用每个Action的execute
方法以完成业务逻辑处理。
2. ModelDriven
ModelDriven接口更为简洁:
public interface ModelDriven {
Object getModel();
}
ModelDriven仅仅定义了一个getModel方法。XWork在调度Action时,将通
过此方法获取Model 对象实例,并根据请求参数为其设定属性值。而此后的
页面返回过程中,XWork 也将调用此方法获取Model 对象实例并将其与设定
的返回界面相融合。
注意这里与Spring MVC 不同,Spring MVC 会自动为逻辑处理单元创建
Command Class实例,但Webwork不会自动为Action创建Model对象实例,
Model 对象实例的创建需要我们在Action 代码中完成(如LoginAction 中
LoginInfo对象实例的创建)。
另外,如代码注释中所描述,登录成功之后,我们随即将username保存在Session之中,
这也是大多数登录操作必不可少的一个操作过程。
这里面牵涉到了Webwork中的一个重要组成部分:ActionContext。
ActionContext为Action提供了与容器交互的途径。对于Web 应用而言,与容器的交互
大多集中在Session、Parameter,通过ActionContext我们在代码中实现与Servlet API无关的
容器交互。
如上面代码中的:
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
同样,我们也可以操作Parameter:
ActionContext ctx = ActionContext.getContext();
Map params = ctx.getParameters();
String username = ctx.getParameters("username");
上述的操作,将由XWork根据当前环境,调用容器相关的访问组件(Web 应用对应的
就是Webwork)完成。上面的ActionContext.getSession(),XWork 实际上将通过Webwork
提供的容器访问代码“HttpServletRequest.getSession()”完成。
注意到,ActionContext.getSession返回的是一个Map类型的数据对象,而非HttpSession。
这是由于WebWork对HttpSession进行了转换,使其转变为与Servlet API无关的Map对象。
通过这样的方式,保证了Xwork 所面向的是一个通用的开放结构。从而使得逻辑层与表现
层无关。增加了代码重用的可能。
此 外, 为了提供与Web 容器直接交互的可能。WebWork 还提供了一个
ServletActionContext实现。它扩展了ActionContext,提供了直接面向Servlet API的容器访
问机制。
我们可以直接通过ServletActionContext.getRequest 得到当前HttpServletRequest 对象的
获得如此灵活性的代价就是,我们的代码从此与ServletAPI 紧密耦合,之后系统在不
同平台之间移植就将面临更多的挑战(同时单元测试也难于进行)。
平台移植的需求并不是每个应用都具备。大部分系统在设计阶段就已经确定其运行平
台,且无太多变更的可能。不过,如果条件允许,尽量通过ActionContext 与容器交互,而
不是平台相关的ServletActionContext,这样在顺利实现功能的同时,也获得了平台迁移上
的潜在优势,何乐而不为。
登录成功界面:
main.jsp:
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<p align="center">Login Success!</p>
<p align="center">Welcome!
<ww:property value="#session['username']"/>
</p>
<p align="center">
<b>Messages:</b><br>
<ww:iterator value="messages" status="index">
<ww:if test="#index.odd == true">
!<ww:property/><br>
</ww:if>
<ww:else>
*<ww:property/><br>
</ww:else>
</ww:iterator>
</p>
</body>
</html>
这里我们引入了Webwork的taglib,如页面代码第一行的申明语句。
下面主要使用了三个tag:
<ww:property value="#session['username']"/>
读取Model对象的属性填充到当前位置。
value指定了需要读取的Model对象的属性名。
这里我们引用了LoginAction在session中保存的’username’对象。
由于对应的Model(LoginInfo)中也保存了username 属性。下面的语句与之
效果相同:
<ww:property value="username"/>
与 JSP2中的EL类似,对于级联对象,这里我们也可以通过“.”操作符获得
其属性值,如value="user.username"将得到Model 对象中所引用的user
对象的username 属性(假设LoginInfo中包含一个User 对象,并拥有一个名
为“username”的属性)。
关于EL的内容比较简单,本文就不再单独开辟章节进行探讨。
Webwork中包括以下几种特殊的EL表达式:
parameter[‘username’] 相当于request.getParameter(“username”);
request[‘username’] 相当于request.getAttribute(“username”);
session[‘username’] 从session中取出以“username”为key的值
application[‘username’] 从ServletContext中取出以“username”为key
的值
注意需要用“#”操作符引用这些特殊表达式。
另外对于常量,需要用单引号包围,如#session['username'] 中的
'username'。
<ww:iterator value="messages" status="index">
迭代器。用于对java.util.Collection、java.util.Iterator、java.util.Enumeration,、
java.util.Map、Array类型的数据集进行循环处理。
其中,value属性的语义与<ww:property>中一致。
而 status属性则指定了循环中的索引变量,在循环中,它将自动递增。
而在下面的<ww:if>中,我们通过“#”操作符引用这个索引变量的值。
索引变量提供了以下几个常用判定方法:
first 当前是否为首次迭代
last 当前是否为最后一次迭代
odd 当前迭代次数是否奇数
even 当前迭代次数是否偶数
<ww:if test="#index.odd == true">和<ww:else>
用于条件判定。
test属性指定了判定表达式。表达式中可通过“#”操作符对变量进行引用。
表达式的编写语法与java 表达式类似。
类似的,还有<ww:elseif test="……">。
登录失败界面
实际上,这个界面即登录界面index.jsp。只是由于之前出于避免干扰的考虑,隐藏了
index.jsp中显示错误信息的部分。
完整的index.jsp如下:
<%@ page pageEncoding="gb2312"
contentType="text/html;charset=gb2312"%>
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<form action="/login.action">
<p align="center">
登录<br>
<ww:if test="errorMessage != null">
<font color="red">
<ww:property value="errorMessage"/>
</font>
</ww:if>
</p>
用户名:
<input type="text" name="model.username" />
<br>
密 码 :
<input type="password" name="model.password" />
<br>
<p align="center">
<input type="submit" value="提交" name="B1"/>
<input type="reset" value="重置" name="B2"/>
</p>
</form>
</body>
</html>
这里首先我们进行判断,如果Model中的errorMessage不为null,则显示错误信息。这
样,在用户第一次登录时,由于Model对象尚未创建,errorMessage自然为null,错误信息
不会显示,即得到了与之前的index.jsp同样的效果。