WEBWORK入门及实例说明
Posted tropica
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WEBWORK入门及实例说明相关的知识,希望对你有一定的参考价值。
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包
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执行结果的输出)等,具体配置如下:
这样,.action结尾的所有url请求将直接有ServletDispatcher去调度。下面我们写一个经典的HelloWorld,跑一个简单实例来验证你运行环境是否可用,并感受一下简单、功能强大的WebWork的开发。
注意:如果使用WebWork自带的标签库,除了配置相应的标签库以外,还须将com.opensymphony.webwork.views.velocity.WebWorkVelocityServlet配置到web.xml,具体可以参考webwork-example里面的配置。
HelloWorld
首先看下面这个程序HelloWorldAction.java:
HelloWorldAction是一个普通的Java类,它实现了Action这个接口。Action是一个非常简单的接口,只有一个方法:public String execute() throws Exception; ,Action类介绍见下一节。HelloWorldAction有一个String类型字段greeting,在execute()方法中,greeting被赋值“Hello World!”,并返回String型常量SUCCESS,SUCCESS的定义详见Action接口,这个常量代表了execute()方法执行成功,将返回成功页面。
返回的页面greetings.jsp代码如下:
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”,表示我们调用这个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
你将会看到如下结果:
Action(动作)
Action介绍
Action在MVC模式中担任控制部分的角色,在WebWork中使用的最多。每个请求的动作都对应于一个相应的Action,一个Action是一个独立的工作单元和控制命令,它必需要实现XWork里的Action接口,实现Action接口的execute()方法。Action接口的代码如下:
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,执行成功返回成功提示的页面(register-result.jsp)并将注册的信息输出。
模型:User.java
控制:RegisterAction.java
视图:register.jsp、register-result.jsp
配置:xwork.xml
User.java:
模型User是一个普通的JavaBean,它包含了用户注册的字段信息,并对每个字段提供相应的set和get方法。下面我们来看看进行用户注册动作的RegisterAction.java:
这个Action是不是特清爽?用户注册就这么几行代码搞定,当然,我们提倡在Action里最好不要实现业务代码,Action的主要功能是提供从请求中取得参数的值,转化成相应的模型,再将模型传递给执行业务操作的对象,比如:将注册的用户信息存储到数据库中,由业务对象执行业务操作,再返回执行的结果。为了简化我们省去了注册的业务逻辑执行步骤。
再看看我们注册信息输入的页面:register.jsp
register.jsp页面其实只是一个普通的HTML页面,它提供了一个表单,用来接受用户输入的注册信息,它唯一特殊的部分就是input输入框定义的name部分,例如:用户姓名用的是“user. username”。这种命名方式代表什么含义?它是必需的吗?后面我们将会给出答案。
RegisterAction正确执行完成之后,会将执行的结果返回到register-result.jsp页面,由它来显示用户在前面页面输入的注册信息。register-result.jsp代码如下:
这个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:
看了前面的介绍,这段配置文件应该不难理解。用户通过注册页面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();,我们用这个方法返回我们的模型对象就可以了。
我们可以将前面的RegisterAction.java改为Model-Driven(模型驱动的)Action:
这时我们输入信息的页面也有了变化:register-model.jsp
我们发现,输入框里的命名发生了变化。它们都少了“user.”这部分信息。
当我们采用Model-Driven(模型驱动的)Action时,它将取得模型对象保存在值堆栈中。“name="username"”就是代表直接调用模型对象的setUsername()方法。
我们Action的在配置文件中,也要给它指定一个拦截器model-driven,它的作用就是将模型对象保存到值堆栈中。关于拦截器的介绍请看下面的章节。
配置文件如下:
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”的值:
为了实现这个功能,我们用了三个步骤:
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就可以在多线程中使用。
我们可以通过ActionContext的静态方法:ActionContext.getContext()来取得当前的ActionContext对象,我们看看这段代码:
一般情况,我们的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里取得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。
代码如下:
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对象里。
下面我们来看看它是如何将request请求的参数和session进行包装的:
这个方法比较简单,它直接调用了HttpServletRequest的方法getParameterMap(),将所有request请求的参数封装到一个Map中。
这个方法取得所有Session中的属性,它调用了com.opensymphony.webwork.dispatcher. SessionMap类,这个类实现了Map接口,在entrySet()方法中列举Session的所有属性,存放在Set中。
4、根据前面获得的namespace、actionName、extraContext,创建一个ActonProxy
默认的proxy是com.opensymphony.xwork.DefaultActionProxy,在它的构造函数会进行下面的操作:
1)、根据namespace、actionName读取xwork.xml配置文件里这个Action的所有配置信息。
2)、创建ActionInvocation
默认的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();, invocation对象的invoke()方法它遍历并执行这个Action对应的所有拦截器,执行Action对应的方法(默认的是execute()),根据Action执行返回的值去调用执行相应的Result(返回结果处理)的方法。
Action的单元测试
理解了ServletDispatcher,我们就明白了整个框架调用执行的顺序。Action虽然是与Web无关,可是它的创建、参数设置、执行与我们的WebWork、XWork紧密关联在一起,有我们的控制器ServletDispatcher去统一调度,那我们如何去对Action进行独立的单元测试呢?
请看下面的例子:使用单元测试框架JUnit对register.User. RegisterAction做单元测试
见example.register. RegisterActionTest类testExecuteWithProxyFactory()方法:
下面解说这个方法:
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提供了多种活灵活视图展现方式。
我们先看看前面用户注册例子的展现方式:我们使用的是Jsp和WebWork自带的标签库,Action对应的视图当然是在xwork.xml配置文件里设置:
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.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.RequestDispatcher的forward()或include()方法调度到页面展现,这样的页面一般是Jsp页面。
参数(Parameters)
是否必需
描 述
location
是
执行完成之后转向的位置
parse
否
默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析
例子:
也可以简单写成这样:
Redirect:将响应重定向到浏览器指定的位置,它将会导致Action执行完成的数据丢失或不再可用。它在程序里是通过调用javax.servlet.http.HttpServletResponse.sendRedirect(String location)方法,将响应定向到参数location指定的、新的url中。
参数(Parameters)
是否必需
描 述
location
是
执行完成之后转向的位置
parse
否
默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析
例子
Action Chaining:一种特殊的视图结果,将Action执行完之后链接到另一个Action中继续执行。新的Action使用上一个Action的上下文(ActionContext)。
参数(Parameters)
是否必需
描 述
actionName
是
将要被链接的Action名字
namespace
否
被链接的Action的命名空间(namespace),如果不设置,默认的即是当前的命名空间
例子:
将要调用的Action如下:
Velocity:它类似Jsp的执行环境(使用JavaServlet容器),将Velocity模板转化成数据流的形式,直接通过JavaServlet输出。
参数(Parameters)
是否必需
描 述
location
是
执行完成之后转向的位置(一般是.vm页面)
parse
否
默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析
例子:
FreeMarker:FreeMarker是一个纯Java模板引擎;一个普通的基于模板生成文本的工具,它只能应用在Web应用环境中。
参数(Parameters)
是否必需
描 述
location
是
执行完成之后转向的位置
parse
否
默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析
contentType
否
如果不指定,默认的是"text/html"
例子:
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
例子:
或者默认的pdf格式
XML/XSL:将结果转换为xml输出
参数(Parameters)
是否必需
描述
location
是
执行完成之后转向的位置
parse
否
默认的是“true”,如果设置为“false”,location参数将不会被OGNL表达式语言解析
例子:
表达式与言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中保存、取得数据的步骤:
我们来看一看它的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类的对象:
以上是关于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包
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.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型常量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
你将会看到如下结果:
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,执行成功返回成功提示的页面(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; /** * @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表达式语言的核心是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:
代码 |
<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();,我们用这个方法返回我们的模型对象就可以了。
我们可以将前面的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介绍
通过上面用户注册例子的学习,我们知道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就可以在多线程中使用。
我们可以通过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里取得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配置文件里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、根据前面获得的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();, 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模型,将其数据与前面设置参数的数据进行比较,判断它是否等于我们预期设置的数值。
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.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.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"> <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
例子:
代码 |
<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> |
表达式与言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(); 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() { return remove(0); } public void push(Object o) { add(0, o); } } |