webwork .doc

WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
WebWork 教程-0.90 版
作者:moxie(安子)
发布记录:
2004-6-29:WebWork 教程0.90 版
说明:本次发布的是初稿部分,除了章节:“实战G-Roller-WW”、“WebWork 与其它开源项
目的集成”,其余章节全部完成。
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
目 录
WebWork 教程-0.90 版..................................................................................................................1
WebWork 介绍..........................................................................................................................3
WebWork 安装-HelloWorld ...................................................................................................3
WebWork 安装..................................................................................................................3
HelloWorld.......................................................................................................................4
Action(动作) .............................................................................................................................6
Action 介绍.......................................................................................................................6
用户注册例子...................................................................................................................6
Field-Driven Action vs. Model-Driven Action................................................................10
ActionContext(Action 上下文)...............................................................................................12
ActionContext 介绍........................................................................................................12
ServletActionContext......................................................................................................13
ServletDispatcher 原理...........................................................................................................14
Action 的单元测试.........................................................................................................17
Result Type .............................................................................................................................18
表达式与言EL 和OGNL......................................................................................................22
OGNL 介绍.....................................................................................................................22
值堆栈-OgnlValueStack...............................................................................................22
Interceptor(拦截器)框架........................................................................................................28
Interceptor 的原理..........................................................................................................31
验证框架................................................................................................................................32
为用户注册添加验证功能.............................................................................................32
XWork 配置详述....................................................................................................................37
xwork.xml 文件的标签元素..........................................................................................37
实战G-Roller-WW.................................................................................................................41
G-Roller-WW 介绍.........................................................................................................41
JSTL 与WebWork 的整合.............................................................................................41
中文解决方案.................................................................................................................41
WebWork 与其它开源项目的集成........................................................................................41
Spring.............................................................................................................................41
Hibernate........................................................................................................................41
Xml-RPC.........................................................................................................................41
总结........................................................................................................................................42
附录........................................................................................................................................42
我钟爱的Opensympnony ..............................................................................................42
从技术的角度Struts1.1 与WebWork2 的比较.............................................................44
WebWork 的项目资源....................................................................................................46
参考资料.........................................................................................................................46
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
WebWork 介绍
WebWork 是由OpenSymphony 组织开发的,致力于组件化和代码重用的拉出式MVC 模式
J2EE Web 框架。WebWork 目前最新版本是2.1,现在的WebWork2.x 前身是Rickard Oberg
开发的WebWork,但现在WebWork 已经被拆分成了Xwork1 和WebWork2 两个项目,如下
示意图所示:
Xwork 简洁、灵活功能强大,它是一个标准的Command 模式实现,并且完全从web 层
脱离出来。Xwork 提供了很多核心功能:前端拦截机(interceptor),运行时表单属性验证,
类型转换,强大的表达式语言(OGNL – the Object Graph Notation Language),IoC(Inversion
of Control 倒置控制)容器等。
WebWork2 建立在Xwork 之上, 处理HTTP 的响应和请求。WebWork2 使用
ServletDispatcher 将HTTP 请求的变成Action(业务层Action 类), session(会话)application
(应用程序)范围的映射,request 请求参数映射。WebWork2 支持多视图表示,视图部分可
以使用JSP, Velocity, FreeMarker, JasperReports,XML 等。
下面我们提到的WebWork 将为WebWork2,使用的版本是2.1。
WebWork 安装-HelloWorld
WebWork 安装
当然,在具体开发使用介绍之前,搭建好运行环境是必备的。
首先从https://webwork.dev.java.net/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 包
WebWork1
XWork1
WebWork2
Web
Non-web
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
src 目录 源程序目录
2、WebWork 是J2EE 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 的一些配
置信息,解析XWork 的Action 配置信息,根据请求去组装和调用执行相应的拦截器
(Interceptor)、Action、Action Result(Action 执行结果的输出)等,具体配置如下:
……
<servlet>
<servlet-name>webwork</servlet-name>
<servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatc
her</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() {
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
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 型常量SUCCESS,SUCCESS 的定义详见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"/>语句,相当于调用相应Action
(HelloWorldAction)的getGreeting()方法,取得变量“greeting”的值。
我们的HelloWorld 代码就这么多,完了。可是,HelloWorldAction 怎么去调用、执行?
执行成功它又怎么知道返回到greetings.jsp?XWork 的配置文件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.action;class=" helloWorld .HelloWorldAction"很好
理解,这是真正调用执行的类。我们在看看result 标签:name="success",记得前面
HelloWorldAction 返回的字符常量SUCCESS 吗?它的值其实就是“success”,它表示Action
执行成功返回success 就转向这个结果;type="dispatcher"表示执行完Action,转向结果页面
的方式;param 参数指定了结果页面的位置:/greetings.jsp。
代码写完,剩下的当然是编译、部署。启动tomcat 服务器之后我们就可以执行了:
在浏览器里输入你的地址:http://localhost:8080/webwork/hello.action
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
你将会看到如下结果:
Action(动作)
Action 介绍
Action 在MVC 模式中担任控制部分的角色,在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()方法执行完成之后,调用哪一种返回结果。
字符常量的含义如下:
SUCCESS:Action 正确的执行完成,返回相应的视图;
NONE:表示Action 正确的执行完成,但并不返回任何视图;
ERROR:表示Action 执行失败,返回到错误处理视图;
INPUT:Action 的执行,需要从前端界面获取参数,INPUT 就是代表这个参数输入的
界面,一般在应用中,会对这些参数进行验证,如果验证没有通过,将自动返回到该视图;
LOGIN:Action 因为用户没有登陆的原因没有正确执行,将返回该登陆视图,要求用
户进行登陆验证。
用户注册例子
下面我们将以一个用户注册的例子详细介绍Action 的原理:
功能描述:一个用户注册页面register.jsp,用户可以在这个页面里输入用户注册的基本
信息(例如:姓名、密码、Email 等),输入完成提交表单,执行用户注册的Action,执行
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
成功返回成功提示的页面(register-result.jsp)并将注册的信息输出。
模型:User.java
控制:RegisterAction.java
视图:register.jsp、register-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,它包含了用户注册的字段信息,并对每个字段提供
相应的set 和get 方法。下面我们来看看进行用户注册动作的RegisterAction.java:
package example.register;
import com.opensymphony.xwork.Action;
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
/**
* @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 页面,它提供了一个表单,用来接受用户输
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
入的注册信息,它唯一特殊的部分就是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 表达式语言的核心是OGNL(Object Graph Notation Language),
OGNL 是一种功能强大,技术成熟,应用广泛的表达式语言,将在下面的章节有详细介绍。
我们在回到前面介绍的register.jsp,Input 输入框
<input type="text" name="user.username">里用的“user.username”,现在我们可以明白,它不
是随意设置的,它是一个表达式语言,有着特殊的功能。看到这里,不知道你心中是否有一
个疑问:我们的RegisterAction 是如何取得用户注册页面输入的数据呢?如果你做过Web 开
发,你一定会想到RegisterAction 里必需有一些从客户端请求中获取参数的语句,例如: 类
似: String username = request.getParameter(“user. username”) 的语句( request 是
HttpServletRequest 的对象),去从request 请求里面获取用户输入的参数值。可是我们这个
Action 里面只有User 对象简单的get 方法,并没有其它的代码。Xwork 框架的Action 是如
何去实现了与Web 无关?request 请求的参数是怎么传递到我们Action 的模型User 中呢?
在回答答案之前,我们先看一看Xwork 的配置文件xwork.xml:
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
<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.RegisterAction,XWork 框架会负责去创
建这个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"/>,这个拦截器将在RegisterAction
的execute()方法执行之前调用,作用是将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 和模型里相应的ge 或
set 方法, 将从请求中取得的值设置到模型中去。register.jsp 中Input 输入框的
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
它很像Struts 的FormBean,但在WebWork 中,只要普通Java 对象就可以充当模型部
分。Model-Driven(模型驱动的)Action 要求我们的Action 实现com.opensymphony.xwork.
ModelDriven 接口,它有一个方法:Object getModel();,我们用这个方法返回我们的模型对
象就可以了。
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
我们可以将前面的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>
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
我们发现,输入框里的命名发生了变化。它们都少了“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 介绍
通过上面用户注册例子的学习,我们知道Xwork 与Web 无关性,我们的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 对象context,ActionContext 是个什么冬冬?
2、 从context 对象里获取我们所有的请求参数,取得的却是一个Map 对象params?
3、 居然可以从我们的Map 对象params 里获取我们需要的request 请求参数
“username”的值。
ActionContext(com.opensymphony.xwork.ActionContext)是Action 执行时的上下文,
上下文可以看作是一个容器(其实我们这里的容器就是一个Map 而已),它存放放的是Action
在执行时需要用到的对象,比如:在使用WebWork 时,我们的上下文放有请求的参数
(Parameter)、会话(Session)、Servlet 上下文(ServletContext)、本地化(Locale)信息等。
在每次执行Action 之前都会创建新的ActionContext,ActionContext 是线程安全的,也就
是说在同一个线程里ActionContext 里的属性是唯一的,这样我的Action 就可以在多线程中
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
使用。
我们可以通过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 需要直接与JavaServlet 的HttpSession、HttpServletRequest 等一些对
象进行操作,我们又该如何处理?请看下面的ServletActionContext。
ServletActionContext
ServletActionContext(com.opensymphony.webwork. ServletActionContext),这个类直接
继承了我们上面介绍的ActionContext,它提供了直接与JavaServlet 相关对象访问的功能,
它可以取得的对象有:
1、 javax.servlet.http.HttpServletRequest:HTTPservlet 请求对象
2、 javax.servlet.http.HttpServletResponse;:HTTPservlet 相应对象
3、 javax.servlet.ServletContext:Servlet 上下文信息
4、 javax.servlet.ServletConfig:Servlet 配置对象
5、 javax.servlet.jsp.PageContext:Http 页面上下文
ServletActionContext 除了提供了上面这些对象访问, 它当然也继承了它父类
ActionContex 的很多功能,比如:对OgnlValueStack、Action 名字等的访问。
下面我们看看几个简单的例子,让我们了解如何从ServletActionContext 里取得
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
JavaServlet 的相关对象:
1、 取得HttpServletRequest 对象:
HttpServletRequest request = ServletActionContext. getRequest();
2、 取得HttpSession 对象:
HttpSession session = ServletActionContext. getRequest().getSession();
ServletActionContext 和ActionContext 有着一些重复的功能,在我们的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 配置文件里
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
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);
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
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、根据前面获得的namespace、actionName、extraContext,创建一个ActonProxy
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace,
actionName, extraContext);
默认的proxy 是com.opensymphony.xwork.DefaultActionProxy,在它的构造函数会进行
下面的操作:1)、根据namespace、actionName 读取xwork.xml 配置文件里这个Action 的所
有配置信息。
2)、创建ActionInvocation
invocation = ActionProxyFactory.getFactory().createActionInvocation(this, extraContext);
默认的invocation 是com.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、执行proxy 的execute()方法,这个方法最核心的语句是:retCode = invocation.invoke();,
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
invocation 对象的invoke()方法它遍历并执行这个Action 对应的所有拦截器,执行Action 对
应的方法(默认的是execute()),根据Action 执行返回的值去调用执行相应的Result(返回
结果处理)的方法。
Action 的单元测试
理解了ServletDispatcher,我们就明白了整个框架调用执行的顺序。Action 虽然是与Web
无关,可是它的创建、参数设置、执行与我们的WebWork、XWork 紧密关联在一起,有我
们的控制器ServletDispatcher 去统一调度,那我们如何去对Action 进行独立的单元测试呢?
请看下面的例子:使用单元测试框架JUnit 对register.User. RegisterAction 做单元测试
见example.register. RegisterActionTest 类testExecuteWithProxyFactory()方法:
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 模型,将其数据与前面设置参数的数据进行比较,判断它是否等于我
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
们预期设置的数值。
Result Type
前面我们学习了ServletDispatcher,它是WebWork 框架机制的核心。它和Action 在我
们MVC 模式中,扮演着控制器的角色,MVC 模式通过控制器实现了我们模型和视图的分
离。WebWork 提供了多种活灵活视图展现方式。
我们先看看前面用户注册例子的展现方式:我们使用的是Jsp 和WebWork 自带的标签
库,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>
Result 是Action 执行完返回的一个字符串常量,它表示Action 执行完成的状态,比如:
执行成功、执行失败等。在我们前面Action 的介绍中,详细介绍了它默认的标准Result,当
然Result 我们也可以自己定义,只要是一个字符串常量就可以了。
Result 的值在xwork.xml 配置文件里就是result 标签里“name”的值,name="success"
表示Action 执行成功,返回“success”就对应此标签的配置,进行视图输出。
“type”就是我们的Result Type,Result Type 是一个类,它在Action 执行完成并返回
Result 之后,决定采用哪一种视图技术,将执行结果展现给用户。我们输出的类型是:
type="dispatcher",它对应com.opensymphony.webwork.dispatcher.ServletDispatcherResult 这个
类,它将执行结果通过javax.servlet.RequestDispatcher 的forward()或include()方法调度到Jsp
页面展现。
我们可以自己开发Result Type,实现我们需要的视图展现方式。Result Type 必需要实现
com.opensymphony.xwork..Result 接口。在WebWork 中,它已经为我们提供了很多Result
Type,实现了视图部分对JSP, Velocity, FreeMarker, JasperReports,XML 等的支持,具体如
下表格:
Result Type Nname Class
Dispatcher dispatcher com.opensymphony.webwork.dispatcher.ServletDispatcherRes
ult
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.FreemarkerRe
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
sult
JasperReports jasper com.opensymphony.webwork.views.jasperreports.JasperRepor
tsResult
XML/XSL xslt com.opensymphony.webwork.views.xslt.XSLTResult
HttpHeader com.opensymphony.webwork.dispatcher.HttpHeaderResult
Dispatcher:通过javax.servlet.RequestDispatcher 的forward()或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">
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
<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>
FreeMarker:FreeMarker 是一个纯Java 模板引擎;一个普通的基于模板生成文本的工具,
它只能应用在Web 应用环境中。
参数(Parameters) 是否必需 描 述
location 是 执行完成之后转向的位置
parse 否 默认的是“true”,如果设置为“false”,location 参数将不
会被OGNL 表达式语言解析
contentType 否 如果不指定,默认的是"text/html"
例子:
<result name="success" type="freemarker">foo.ftl</result>
JasperReports : 将Action 执行的结果通过JasperReports 报表形式输出,可以指定
JasperReports 支持的输出格式(PDF、HTML、XLS、CSV、XML 等),默认是通过PDF 格
式输出。
参数(Parameters) 是否必需 描 述
location 是 执行完成之后转向的位置
parse 否 默认的是“true”,如果设置为“false”,location 参数将不
会被OGNL 表达式语言解析
dataSource 是 它是Action 的一个字段(通常是一个List),OGNL 表达式
被用来去value stack(OgnlValueStack)重新找回这个
dataSource
format 否 报表生成的数据格式,默认的是pdf
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
例子:
<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>
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
表达式与言EL 和OGNL
OGNL 介绍
OGNL 是Object-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.ognl.org,
那里有很详细的文档。
值堆栈-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();
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
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、 创建我们的Action(RegisterAction)类的对象action,将action 对象压入堆栈
valueStack 中。在WebWrok 中Action 的创建、入栈是在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() {
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
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'] =
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
"+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();
}
}
/*
WebWork 教程
有任何意见和建议,请email to:achqian@hotmail.com(MSN)
版权所有,请勿转载和篡改
* 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 getUse
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值