介紹
WebWork是由OpenSymphony組織開發(fā)的,致力于組件化和代碼重用的拉出式MVC模式J2EE Web框架。WebWork目前最新版本是2.1,現(xiàn)在的WebWork2.x前身是Rickard Oberg開發(fā)的WebWork,但現(xiàn)在WebWork已經(jīng)被拆分成了Xwork1和WebWork2兩個(gè)項(xiàng)目,如下示意圖所示:
WebWork1
XWork1
WebWork2
Web
Non-web
Xwork簡(jiǎn)潔、靈活功能強(qiáng)大,它是一個(gè)標(biāo)準(zhǔn)的Command模式實(shí)現(xiàn),并且完全從web層脫離出來。Xwork提供了很多核心功能:前端攔截機(jī)(interceptor),運(yùn)行時(shí)表單屬性驗(yàn)證,類型轉(zhuǎn)換,強(qiáng)大的表達(dá)式語言(OGNL – the Object Graph Notation Language),IoC(Inversion of Control倒置控制)容器等。
WebWork2建立在Xwork之上,處理HTTP的響應(yīng)和請(qǐng)求。WebWork2使用ServletDispatcher將HTTP請(qǐng)求的變成Action(業(yè)務(wù)層Action類), session(會(huì)話)application(應(yīng)用程序)范圍的映射,request請(qǐng)求參數(shù)映射。WebWork2支持多視圖表示,視圖部分可以使用JSP, Velocity, FreeMarker, JasperReports,XML等。
下面我們提到的WebWork將為WebWork2,使用的版本是2.1。
安裝-HelloWorld
安裝
當(dāng)然,在具體開發(fā)使用介紹之前,搭建好運(yùn)行環(huán)境是必備的。
首先從
https://webwork.dev.java.net/servlets/ProjectDocumentList下載最新的WebWork壓縮包,并將其解壓開來。打開解壓目錄,你將看到以下的文件和目錄:
webwork-2.x.jar 當(dāng)然就是WebWrok最新發(fā)布的Jar包
webwork-example.war 是WebWrok自帶的很有代表性的功能演示例子,掌握它是提高你的WebWork技術(shù)水平的捷徑
webwork-migration.jar 提供快速將1.x版本移植到2.x版本所用的類文件
docs目錄 WebWrok的使用文檔,包括api文檔、clover文檔、單元測(cè)試(Junit)文檔等
lib目錄 WebWork在運(yùn)行或編譯時(shí)所用到的所有.jar包
src目錄 源程序目錄
2、WebWork是J2EE Web框架,當(dāng)然要運(yùn)行在Web容器中,我用的是穩(wěn)定的Tomcat 4.1,關(guān)于tomcat的安裝和部署請(qǐng)自己搞定。
3、用WebWork當(dāng)然要將它的運(yùn)行時(shí)用到的Jar包放到Web容器可以找到的ClassPath中,將步驟1介紹的webwork-2.x.jar放到你部署目錄下WEB-INF\lib目錄里,同時(shí)將WebWrok解壓目錄lib\core下的所有.jar文件也拷貝到WEB-INF\lib目錄,這些是運(yùn)行WebWork必需要用到的jar包。
4、了解Web框架的朋友都知道,一般Web框架都是通過一個(gè)JavaServlet控制器提供統(tǒng)一的請(qǐng)求入口,解析請(qǐng)求的url,再去調(diào)用相應(yīng)的Action進(jìn)行業(yè)務(wù)處理。WebWork也不例外,它要求你在web.xml文件里配置一個(gè)派遣器ServletDispatcher,它初始化WebWrok的一些配置信息,解析XWork的Action配置信息,根據(jù)請(qǐng)求去組裝和調(diào)用執(zhí)行相應(yīng)的攔截器(Interceptor)、Action、Action Result(Action執(zhí)行結(jié)果的輸出)等,具體配置如下:
……
<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結(jié)尾的所有url請(qǐng)求將直接有ServletDispatcher去調(diào)度。下面我們寫一個(gè)經(jīng)典的HelloWorld,跑一個(gè)簡(jiǎn)單實(shí)例來驗(yàn)證你運(yùn)行環(huán)境是否可用,并感受一下簡(jiǎn)單、功能強(qiáng)大的WebWork的開發(fā)。
注意:如果使用WebWork自帶的標(biāo)簽庫,除了配置相應(yīng)的標(biāo)簽庫以外,還須將com.opensymphony.webwork.views.velocity.WebWorkVelocityServlet配置到web.xml,具體可以參考webwork-example里面的配置。
首先看下面這個(gè)程序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是一個(gè)普通的Java類,它實(shí)現(xiàn)了Action這個(gè)接口。Action是一個(gè)非常簡(jiǎn)單的接口,只有一個(gè)方法:public String execute() throws Exception; ,Action類介紹見下一節(jié)。HelloWorldAction有一個(gè)String類型字段greeting,在execute()方法中,greeting被賦值“Hello World!”,并返回String型常量SUCCESS,SUCCESS的定義詳見Action接口,這個(gè)常量代表了execute()方法執(zhí)行成功,將返回成功頁面。
返回的頁面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很簡(jiǎn)單的jsp頁面,它使用了WebWork自帶的標(biāo)簽庫。它的作用是輸出變量“greeting”的值。這個(gè)<ww:property value="greeting"/>語句,相當(dāng)于調(diào)用相應(yīng)Action(HelloWorldAction)的getGreeting()方法,取得變量“greeting”的值。
我們的HelloWorld代碼就這么多,完了。可是,HelloWorldAction怎么去調(diào)用、執(zhí)行?執(zhí)行成功它又怎么知道返回到greetings.jsp?XWork的配置文件xwork.xml會(huì)負(fù)責(zé)將要執(zhí)行的Action和展現(xiàn)的視圖連接起來,見xwork.xml的如下片斷:
<action name="hello" class=" helloWorld .HelloWorldAction">
<result name="success" type="dispatcher">
<param name="location">/greetings.jsp</param>
</result>
</action>
我們先看action標(biāo)簽:name=”hello”,表示我們調(diào)用這個(gè)Action的標(biāo)識(shí)是hello,這樣我們可以通過下面的url訪問這個(gè)Action:…/hello.action,
例如:
http://localhost:8080/webwork/hello.action;class=" helloWorld .HelloWorldAction"很好理解,這是真正調(diào)用執(zhí)行的類。我們?cè)诳纯磖esult標(biāo)簽:name="success",記得前面HelloWorldAction返回的字符常量SUCCESS嗎?它的值其實(shí)就是“success”,它表示Action執(zhí)行成功返回success就轉(zhuǎn)向這個(gè)結(jié)果;type="dispatcher"表示執(zhí)行完Action,轉(zhuǎn)向結(jié)果頁面的方式;param參數(shù)指定了結(jié)果頁面的位置:/greetings.jsp。
代碼寫完,剩下的當(dāng)然是編譯、部署。啟動(dòng)tomcat服務(wù)器之后我們就可以執(zhí)行了:
在瀏覽器里輸入你的地址:
http://localhost:8080/webwork/hello.action你將會(huì)看到如下結(jié)果:
動(dòng)作)
介紹
Action在MVC模式中擔(dān)任控制部分的角色,在WebWork中使用的最多。每個(gè)請(qǐng)求的動(dòng)作都對(duì)應(yīng)于一個(gè)相應(yīng)的Action,一個(gè)Action是一個(gè)獨(dú)立的工作單元和控制命令,它必需要實(shí)現(xiàn)XWork里的Action接口,實(shí)現(xiàn)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類里最重要的部分,它執(zhí)行返回String類型的值,在Action中返回的值一般使用它上面定義的標(biāo)準(zhǔn)靜態(tài)字符常量。例如:前面的HelloWorldAction返回的就是SUCCESS字符常量,真正的值當(dāng)然就是“success”,它與xwork配置文件里result標(biāo)簽name的值是相對(duì)應(yīng)的。它用來決定execute()方法執(zhí)行完成之后,調(diào)用哪一種返回結(jié)果。字符常量的含義如下:
SUCCESS:Action正確的執(zhí)行完成,返回相應(yīng)的視圖;
NONE:表示Action正確的執(zhí)行完成,但并不返回任何視圖;
ERROR:表示Action執(zhí)行失敗,返回到錯(cuò)誤處理視圖;
INPUT:Action的執(zhí)行,需要從前端界面獲取參數(shù),INPUT就是代表這個(gè)參數(shù)輸入的界面,一般在應(yīng)用中,會(huì)對(duì)這些參數(shù)進(jìn)行驗(yàn)證,如果驗(yàn)證沒有通過,將自動(dòng)返回到該視圖;
LOGIN:Action因?yàn)橛脩魶]有登陸的原因沒有正確執(zhí)行,將返回該登陸視圖,要求用戶進(jìn)行登陸驗(yàn)證。
下面我們將以一個(gè)用戶注冊(cè)的例子詳細(xì)介紹Action的原理:
功能描述:一個(gè)用戶注冊(cè)頁面register.jsp,用戶可以在這個(gè)頁面里輸入用戶注冊(cè)的基本信息(例如:姓名、密碼、Email等),輸入完成提交表單,執(zhí)行用戶注冊(cè)的Action,執(zhí)行成功返回成功提示的頁面(register-result.jsp)并將注冊(cè)的信息輸出。
模型: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是一個(gè)普通的JavaBean,它包含了用戶注冊(cè)的字段信息,并對(duì)每個(gè)字段提供相應(yīng)的set和get方法。下面我們來看看進(jìn)行用戶注冊(cè)動(dòng)作的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);
//在這里調(diào)用用戶注冊(cè)的業(yè)務(wù)邏輯,比如:將注冊(cè)信息存儲(chǔ)到數(shù)據(jù)庫
return SUCCESS;
}
}
這個(gè)Action是不是特清爽?用戶注冊(cè)就這么幾行代碼搞定,當(dāng)然,我們提倡在Action里最好不要實(shí)現(xiàn)業(yè)務(wù)代碼,Action的主要功能是提供從請(qǐng)求中取得參數(shù)的值,轉(zhuǎn)化成相應(yīng)的模型,再將模型傳遞給執(zhí)行業(yè)務(wù)操作的對(duì)象,比如:將注冊(cè)的用戶信息存儲(chǔ)到數(shù)據(jù)庫中,由業(yè)務(wù)對(duì)象執(zhí)行業(yè)務(wù)操作,再返回執(zhí)行的結(jié)果。為了簡(jiǎn)化我們省去了注冊(cè)的業(yè)務(wù)邏輯執(zhí)行步驟。
再看看我們注冊(cè)信息輸入的頁面: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頁面其實(shí)只是一個(gè)普通的HTML頁面,它提供了一個(gè)表單,用來接受用戶輸入的注冊(cè)信息,它唯一特殊的部分就是input輸入框定義的name部分,例如:用戶姓名用的是“user. username”。這種命名方式代表什么含義?它是必需的嗎?后面我們將會(huì)給出答案。
RegisterAction正確執(zhí)行完成之后,會(huì)將執(zhí)行的結(jié)果返回到register-result.jsp頁面,由它來顯示用戶在前面頁面輸入的注冊(cè)信息。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>
這個(gè)Jsp頁面使用了WebWork的標(biāo)簽庫 <ww:property />,記得HelloWorld里的greetings.jsp嗎?它也使用了這個(gè)標(biāo)簽庫。我們看這個(gè):<ww:property value="user.username"/>
它是一個(gè)普通的使用標(biāo)簽庫語句,查看這個(gè)標(biāo)簽庫的源程序,見包
com.opensymphony.webwork.views.jsp里的PropertyTag.java文件,你會(huì)發(fā)現(xiàn)這個(gè)類會(huì)根據(jù)value后面賦予的表達(dá)式值,去OgnlValueStack里查找這個(gè)表達(dá)式值所對(duì)應(yīng)的操作。執(zhí)行這個(gè)語句OgnlValueStack會(huì)根據(jù)value的值(一個(gè)表達(dá)式)“user.username”去分別調(diào)用RegisterAction類的getUser()和User類的getUsername()方法,即:getUser().getUsername(),取得的數(shù)據(jù)就是前面注冊(cè)頁面輸入的用戶名。
我們把“user.username”這樣的語句叫做表達(dá)式語言(Expression Language,簡(jiǎn)稱為EL)。它由XWork框架提供,XWork表達(dá)式語言的核心是OGNL(Object Graph Notation Language),OGNL是一種功能強(qiáng)大,技術(shù)成熟,應(yīng)用廣泛的表達(dá)式語言,將在下面的章節(jié)有詳細(xì)介紹。
我們?cè)诨氐角懊娼榻B的register.jsp,Input輸入框
<input type="text" name="user.username">里用的“user.username”,現(xiàn)在我們可以明白,它不是隨意設(shè)置的,它是一個(gè)表達(dá)式語言,有著特殊的功能??吹竭@里,不知道你心中是否有一個(gè)疑問:我們的RegisterAction是如何取得用戶注冊(cè)頁面輸入的數(shù)據(jù)呢?如果你做過Web開發(fā),你一定會(huì)想到RegisterAction里必需有一些從客戶端請(qǐng)求中獲取參數(shù)的語句,例如: 類似:String username = request.getParameter(“user. username”)的語句(request是HttpServletRequest的對(duì)象),去從request請(qǐng)求里面獲取用戶輸入的參數(shù)值??墒俏覀冞@個(gè)Action里面只有User對(duì)象簡(jiǎn)單的get方法,并沒有其它的代碼。Xwork框架的Action是如何去實(shí)現(xiàn)了與Web無關(guān)?request請(qǐng)求的參數(shù)是怎么傳遞到我們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>
看了前面的介紹,這段配置文件應(yīng)該不難理解。用戶通過注冊(cè)頁面register.jsp輸入自己的注冊(cè)信息,提交表單到動(dòng)作register.action,它將有ServletDispatcher調(diào)度,從配置文件xwork.xml里查找與“register”匹配的Action名字,即上面配置的Action。通過這個(gè)名字XWork框架找到這個(gè)Action的類:example.register.RegisterAction,XWork框架會(huì)負(fù)責(zé)去創(chuàng)建這個(gè)Action類的對(duì)象并調(diào)用execute()方法進(jìn)行用戶注冊(cè)操作。正確執(zhí)行execute()方法返回String類型數(shù)據(jù)“success”之后,它會(huì)請(qǐng)求再派遣到register-result.jsp頁面。
在這段配置文件里,你一定注意到了它特殊的一句:<interceptor-ref name="params"/>,interceptor-ref標(biāo)簽設(shè)置這個(gè)Action用到的攔截器(Interceptor),“params”引用的是配置文件中的<interceptor name="params" class="
com.opensymphony.xwork.interceptor.ParametersInterceptor"/>,這個(gè)攔截器將在RegisterAction的execute()方法執(zhí)行之前調(diào)用,作用是將request請(qǐng)求的參數(shù)值通過表達(dá)式語言設(shè)置到相應(yīng)RegisterAction的模型里。例如:register.jsp里的<input type="text" name="user.username">,它輸入的值會(huì)由RegisterAction類的getUser()和User類的setUserName(“…”)設(shè)置到這個(gè)User模型里。假設(shè)你在注冊(cè)頁面輸入用戶名“moxie”,提交表單ParametersInterceptor就會(huì)下面的操作:首先從請(qǐng)求中取得參數(shù)的名字和名字對(duì)應(yīng)的值,分別為:“user.username”和“moxie”,根據(jù)這個(gè)名字,從OgnlValueStack中取得堆棧最上面的getUser().setUsername(“moxie”)操作,即取得RegisterAction對(duì)象的User模型,并設(shè)置username屬性的值為“moxie”。
原來,我們的Action是通過XWork的攔截器ParametersInterceptor從提交的表單中取得請(qǐng)求的參數(shù)和值,再通過OgnlValueStack來執(zhí)行表達(dá)式,調(diào)用Action和模型里相應(yīng)的ge或set方法,將從請(qǐng)求中取得的值設(shè)置到模型中去。register.jsp中Input輸入框的name="user.username"是必需要遵守OGNL的命名規(guī)則。也正是很多攔截器的使用,使得我們的Action類和Web實(shí)現(xiàn)了完全的解耦,讓我們的Action能如此的簡(jiǎn)單、優(yōu)雅,攔截器的原理后面章節(jié)我們也將會(huì)有詳細(xì)的介紹。
羅索了這么多,你一定是精通了這個(gè)用戶注冊(cè)的例子了吧!呵呵!
Action根據(jù)FormBean的不同可以分為二類,
一類是Field-Driven(字段驅(qū)動(dòng)的)Action
Action將直接用自己的字段來充當(dāng)FormBean的功能,我們的例子就是使用這種方式。它一般用在頁面表單比較簡(jiǎn)單的情況使用,而且可以直接用域?qū)ο笞鳛锳ction的字段,這樣就不用在另寫FormBean,減少了重復(fù)代碼。
另一類是Model-Driven(模型驅(qū)動(dòng)的)Action
它很像Struts的FormBean,但在WebWork中,只要普通Java對(duì)象就可以充當(dāng)模型部分。Model-Driven(模型驅(qū)動(dòng)的)Action要求我們的Action實(shí)現(xiàn)com.opensymphony.xwork. ModelDriven接口,它有一個(gè)方法:Object getModel();,我們用這個(gè)方法返回我們的模型對(duì)象就可以了。
我們可以將前面的RegisterAction.java改為Model-Driven(模型驅(qū)動(dòng)的)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);
//在這里調(diào)用用戶注冊(cè)的業(yè)務(wù)邏輯,比如:將注冊(cè)信息存儲(chǔ)到數(shù)據(jù)庫
return SUCCESS;
}
public Object getModel() {
return user;
}
}
這時(shí)我們輸入信息的頁面也有了變化: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>
我們發(fā)現(xiàn),輸入框里的命名發(fā)生了變化。它們都少了“user.”這部分信息。
當(dāng)我們采用Model-Driven(模型驅(qū)動(dòng)的)Action時(shí),它將取得模型對(duì)象保存在值堆棧中。“name="username"”就是代表直接調(diào)用模型對(duì)象的setUsername()方法。
我們Action的在配置文件中,也要給它指定一個(gè)攔截器model-driven,它的作用就是將模型對(duì)象保存到值堆棧中。關(guān)于攔截器的介紹請(qǐng)看下面的章節(jié)。
配置文件如下:
<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>
上下文)
介紹
通過上面用戶注冊(cè)例子的學(xué)習(xí),我們知道Xwork與Web無關(guān)性,我們的Action不用去依賴于任何Web容器,不用和那些JavaServlet復(fù)雜的請(qǐng)求(Request)、響應(yīng)(Response)關(guān)聯(lián)在一起。對(duì)請(qǐng)求(Request)的參數(shù)(Param),可以使用攔截器框架自動(dòng)調(diào)用一些get()和set()方法設(shè)置到對(duì)應(yīng)的Action的字段中。但是,僅僅取得請(qǐng)求參數(shù)的值就能完全滿足我們的功能要求嗎?不,在Web應(yīng)用程序開發(fā)中,除了將請(qǐng)求參數(shù)自動(dòng)設(shè)置到Action的字段中,我們往往也需要在Action里直接獲取請(qǐng)求(Request)或會(huì)話(Session)的一些信息,甚至需要直接對(duì)JavaServlet Http的請(qǐng)求(HttpServletRequest)、響應(yīng)(HttpServletResponse)操作。
帶著這些問題,我們來看看下面的一個(gè)功能需求:
我們需要在Action中取得request請(qǐng)求參數(shù)“username”的值:
ActionContext context = ActionContext.getContext();
Map params = context.getParameters();
String username = (String) params.get(“username”);
為了實(shí)現(xiàn)這個(gè)功能,我們用了三個(gè)步驟:
1、 取得我們當(dāng)前的ActionContext對(duì)象context,ActionContext是個(gè)什么冬冬?
2、 從context對(duì)象里獲取我們所有的請(qǐng)求參數(shù),取得的卻是一個(gè)Map對(duì)象params?
3、 居然可以從我們的Map對(duì)象params里獲取我們需要的request請(qǐng)求參數(shù)“username”的值。
ActionContext(com.opensymphony.xwork.ActionContext)是Action執(zhí)行時(shí)的上下文,上下文可以看作是一個(gè)容器(其實(shí)我們這里的容器就是一個(gè)Map而已),它存放放的是Action在執(zhí)行時(shí)需要用到的對(duì)象,比如:在使用WebWork時(shí),我們的上下文放有請(qǐng)求的參數(shù)(Parameter)、會(huì)話(Session)、Servlet上下文(ServletContext)、本地化(Locale)信息等。
在每次執(zhí)行Action之前都會(huì)創(chuàng)建新的ActionContext,ActionContext是線程安全的,也就是說在同一個(gè)線程里ActionContext里的屬性是唯一的,這樣我的Action就可以在多線程中使用。
我們可以通過ActionContext的靜態(tài)方法:ActionContext.getContext()來取得當(dāng)前的ActionContext對(duì)象,我們看看這段代碼:
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();來獲取的。我們?cè)賮砜纯催@里的actionContext對(duì)象的創(chuàng)建:static ThreadLocal actionContext = new ActionContextThreadLocal();,ActionContextThreadLocal是實(shí)現(xiàn)ThreadLocal的一個(gè)內(nèi)部類。ThreadLocal可以命名為“線程局部變量”,它為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,使每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。這樣,我們ActionContext里的屬性只會(huì)在對(duì)應(yīng)的當(dāng)前請(qǐng)求線程中可見,從而保證它是線程安全的。
下面我們看看怎么通過ActionContext取得我們的HttpSession:
Map session = ActionContext.getContext().getSession();
原來我們?nèi)〉玫膕ession卻是Map類型的對(duì)象,這是為什么?原來,我們的WebWork框架將與Web相關(guān)的很多對(duì)象重新進(jìn)行了包裝,比如這里就將HttpSession對(duì)象重新包裝成了一個(gè)Map對(duì)象,供我們的Action使用,而不用直接和底層的HttpSession打交道。也正是框架的包裝,讓我們的Actoion可以完全的和Web層解藕。
如果我們的Action需要直接與JavaServlet的HttpSession、HttpServletRequest等一些對(duì)象進(jìn)行操作,我們又該如何處理?請(qǐng)看下面的ServletActionContext。
ServletActionContext(com.opensymphony.webwork. ServletActionContext),這個(gè)類直接繼承了我們上面介紹的ActionContext,它提供了直接與JavaServlet相關(guān)對(duì)象訪問的功能,它可以取得的對(duì)象有:
1、 javax.servlet.http.HttpServletRequest:HTTPservlet請(qǐng)求對(duì)象
2、 javax.servlet.http.HttpServletResponse;:HTTPservlet相應(yīng)對(duì)象
3、 javax.servlet.ServletContext:Servlet 上下文信息
4、 javax.servlet.ServletConfig:Servlet配置對(duì)象
5、 javax.servlet.jsp.PageContext:Http頁面上下文
ServletActionContext除了提供了上面這些對(duì)象訪問,它當(dāng)然也繼承了它父類ActionContex的很多功能,比如:對(duì)OgnlValueStack、Action名字等的訪問。
下面我們看看幾個(gè)簡(jiǎn)單的例子,讓我們了解如何從ServletActionContext里取得JavaServlet的相關(guān)對(duì)象:
1、 取得HttpServletRequest對(duì)象:
HttpServletRequest request = ServletActionContext. getRequest();
2、 取得HttpSession對(duì)象:
HttpSession session = ServletActionContext. getRequest().getSession();
ServletActionContext和ActionContext有著一些重復(fù)的功能,在我們的Action中,該如何去抉擇呢?我們遵循的原則是:如果ActionContext能夠?qū)崿F(xiàn)我們的功能,那最好就不要使用ServletActionContext,讓我們的Action盡量不要直接去訪問JavaServlet的相關(guān)對(duì)象。在使用ActionContext時(shí)有一點(diǎn)要注意:不要在Action的構(gòu)造函數(shù)里使用ActionContext.getContext(),因?yàn)檫@個(gè)時(shí)候ActionContext里的一些值也許沒有設(shè)置,這時(shí)通過ActionContext取得的值也許是null。
原理
ServletDispatcher是默認(rèn)的處理Web Http請(qǐng)求的調(diào)度器,它是一個(gè)JavaServlet,是WebWork框架的控制器。所有對(duì)Action調(diào)用的請(qǐng)求都將通過這個(gè)ServletDispatcher調(diào)度。它將在web.xml里配置ServletDispatcher時(shí)指定,讓所有對(duì)WebWork 的Action(默認(rèn)的是.action的后綴)的請(qǐng)求都對(duì)應(yīng)到該調(diào)度的JavaServlet中,具體配置在前面的WebWork安裝中有介紹。
ServletDispatcher接受客戶端的HTTP請(qǐng)求,將JavaServlet的很多相關(guān)對(duì)象進(jìn)行包裝,再傳給我們的XWork框架,由我們的XWork框架去解析我們的xwork.xml配置文件,根據(jù)配置文件的信息,創(chuàng)建對(duì)應(yīng)的Action,組裝并調(diào)用相應(yīng)的攔截器,執(zhí)行Action,返回執(zhí)行結(jié)果。WebWork使用XWork的核心,主要是由這個(gè)ServletDispatcher去實(shí)現(xiàn)的,
ServletDispatcher的主要功能調(diào)用如下:
一、init()方法在服務(wù)器啟動(dòng)時(shí)調(diào)用,
1、初始化Velocity引擎
2、檢查是否支持配置文件重新載入功能。如果webwork.configuration.xml.reload(見webwork.properties文件)設(shè)置為true,每個(gè)request請(qǐng)求都將重新裝載xwork.xml配置文件。在開發(fā)環(huán)境使用將會(huì)非常方便,但在生產(chǎn)環(huán)境必需設(shè)置為false。
代碼如下:
if ("true".equalsIgnoreCase(Configuration.getString("webwork.configuration.xml.reload"))) {
FileManager.setReloadingConfigs(true);
}
3、設(shè)置一些文件上傳的信息,比如:上傳臨時(shí)目錄,上傳的最大字節(jié)等。都設(shè)置在webwork.properties文件里,如果在classpath中找不到這個(gè)屬性文件,它會(huì)去讀取默認(rèn)的default.properties
二、service()方法,每次客戶端的請(qǐng)求都將調(diào)用此方法。
1、通過request請(qǐng)求取得action的命名空間(namespace,與xwork.xml配置文件里package標(biāo)簽的name對(duì)應(yīng))
例如:/foo/bar/MyAction.action,取得的命名空間為/foo/bar
在xwork.xml配置文件里應(yīng)該有這一段:
<package name="foo.bar" …….
2、根據(jù)servlet請(qǐng)求的Path,解析出要調(diào)用該請(qǐng)求的Action的名字(actionName),例如:(../foo/bar/MyAction.action -> MyAction)
在xwork.xml配置文件里應(yīng)該有:
<package name="foo.bar" …….
<Action name=” MyAction”……
3、 創(chuàng)建Action上下文(extraContext)。我們前面介紹的ActionContext上下文的對(duì)象,就是在這里設(shè)置的。它將JavaServlet相關(guān)的對(duì)象進(jìn)行包裝,放入到extraContext這個(gè)Map對(duì)象里。
/**
* 將所有的應(yīng)用請(qǐng)求和servlet屬性保存到一個(gè)HashMap中,
* @param requestMap 存放所有request請(qǐng)求屬性的Map
* @param parameterMap 存放所有request請(qǐng)求參數(shù)的Map
* @param sessionMap存放所有session屬性的Map
* @param applicationMap 存放所有servlet上下文屬性的Map
* @param request HttpServletRequest 對(duì)象
* @param response HttpServletResponse 對(duì)象.
* @param servletConfig ServletConfig 對(duì)象.
* @return代表Action 上下文的一個(gè) 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請(qǐng)求的參數(shù)和session進(jìn)行包裝的:
protected Map getParameterMap(HttpServletRequest request) throws IOException {
return request.getParameterMap();
}
這個(gè)方法比較簡(jiǎn)單,它直接調(diào)用了HttpServletRequest的方法getParameterMap(),將所有request請(qǐng)求的參數(shù)封裝到一個(gè)Map中。
protected Map getSessionMap(HttpServletRequest request) {
return new SessionMap(request);
}
這個(gè)方法取得所有Session中的屬性,它調(diào)用了com.opensymphony.webwork.dispatcher. SessionMap類,這個(gè)類實(shí)現(xiàn)了Map接口,在entrySet()方法中列舉Session的所有屬性,存放在Set中。
4、根據(jù)前面獲得的namespace、actionName、extraContext,創(chuàng)建一個(gè)ActonProxy
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);
默認(rèn)的proxy是com.opensymphony.xwork.DefaultActionProxy,在它的構(gòu)造函數(shù)會(huì)進(jìn)行下面的操作:1)、根據(jù)namespace、actionName讀取xwork.xml配置文件里這個(gè)Action的所有配置信息。
2)、創(chuàng)建ActionInvocation
invocation = ActionProxyFactory.getFactory().createActionInvocation(this, extraContext);
默認(rèn)的invocation是com.opensymphony.xwork.DefaultActionInvocation,它的構(gòu)造函數(shù)操作有:
a) 由com.opensymphony.xwork.ObjectFactory創(chuàng)建我們配置文件描述的Action對(duì)象。再將這個(gè)Action對(duì)象存放入OgnlValueStack中。記得我們前面用戶注冊(cè)的例子嗎?當(dāng)用戶提交表達(dá)時(shí)它會(huì)有表達(dá)式語言向OgnlValueStack取得Action對(duì)象的字段,再把輸入框的數(shù)據(jù)設(shè)置到對(duì)應(yīng)的Action字段中,這個(gè)Action對(duì)象就是在這個(gè)時(shí)候進(jìn)棧的。
b) 傳入extraContext參數(shù),創(chuàng)建與ActionInvocation對(duì)應(yīng)的Action上下文(ActionContext)。記得我們?cè)诮榻BActionContext的最后,提出了一個(gè)需要注意的地方:不要在Action構(gòu)造函數(shù)中調(diào)用ActionContext.getContext()?,F(xiàn)在應(yīng)該能明白,原來是Action對(duì)象實(shí)例在ActionContext對(duì)象實(shí)例之前創(chuàng)建的,所有這樣取得ActionContext容器對(duì)象就有可能會(huì)返回null
c) 取得這個(gè)Action對(duì)應(yīng)的所有攔截器(Interceptor),存放入java.util.Iterator對(duì)象中。
5、執(zhí)行proxy的execute()方法,這個(gè)方法最核心的語句是:retCode = invocation.invoke();, invocation對(duì)象的invoke()方法它遍歷并執(zhí)行這個(gè)Action對(duì)應(yīng)的所有攔截器,執(zhí)行Action對(duì)應(yīng)的方法(默認(rèn)的是execute()),根據(jù)Action執(zhí)行返回的值去調(diào)用執(zhí)行相應(yīng)的Result(返回結(jié)果處理)的方法。
的單元測(cè)試
理解了ServletDispatcher,我們就明白了整個(gè)框架調(diào)用執(zhí)行的順序。Action雖然是與Web無關(guān),可是它的創(chuàng)建、參數(shù)設(shè)置、執(zhí)行與我們的WebWork、XWork緊密關(guān)聯(lián)在一起,有我們的控制器ServletDispatcher去統(tǒng)一調(diào)度,那我們?nèi)绾稳?duì)Action進(jìn)行獨(dú)立的單元測(cè)試呢?
請(qǐng)看下面的例子:使用單元測(cè)試框架JUnit對(duì)register.User. RegisterAction做單元測(cè)試
見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);
}
下面解說這個(gè)方法:
1、 對(duì)象params表示請(qǐng)求參數(shù)的Map,在它里面設(shè)置了注冊(cè)用戶的信息。extraContext當(dāng)然就是我們ActionContext上下文的容器,它里面保存了放置請(qǐng)求參數(shù)的對(duì)象params
2、 創(chuàng)建我們的ActionProxy,它傳入的參數(shù)有:“example”-這個(gè)Action的命名空間,“register”-Action對(duì)應(yīng)的名字,extraContext-存放Actin上下文里的對(duì)象,,執(zhí)行并將它返回的值與“success”比較,測(cè)試Action是否能正確執(zhí)行完成。注意:proxy.setExecuteResult(false);,因?yàn)槲覀兪菃卧獪y(cè)試,所以Action執(zhí)行完成就可以了,不用再去調(diào)用結(jié)果響應(yīng)的操作,故將是否執(zhí)行結(jié)果設(shè)置為“false”。
3、 Action正確執(zhí)行完成之后,我們也可以測(cè)試現(xiàn)在Action的字段里的數(shù)據(jù)是否按照我們預(yù)期的要求正確設(shè)置。從ActionProxy對(duì)象里取得執(zhí)行的Action,即RegisterAction對(duì)象,再取得它的User模型,將其數(shù)據(jù)與前面設(shè)置參數(shù)的數(shù)據(jù)進(jìn)行比較,判斷它是否等于我們預(yù)期設(shè)置的數(shù)值。
前面我們學(xué)習(xí)了ServletDispatcher,它是WebWork框架機(jī)制的核心。它和Action在我們MVC模式中,扮演著控制器的角色,MVC模式通過控制器實(shí)現(xiàn)了我們模型和視圖的分離。WebWork提供了多種活靈活視圖展現(xiàn)方式。
我們先看看前面用戶注冊(cè)例子的展現(xiàn)方式:我們使用的是Jsp和WebWork自帶的標(biāo)簽庫,Action對(duì)應(yīng)的視圖當(dāng)然是在xwork.xml配置文件里設(shè)置:
<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執(zhí)行完返回的一個(gè)字符串常量,它表示Action執(zhí)行完成的狀態(tài),比如:執(zhí)行成功、執(zhí)行失敗等。在我們前面Action的介紹中,詳細(xì)介紹了它默認(rèn)的標(biāo)準(zhǔn)Result,當(dāng)然Result我們也可以自己定義,只要是一個(gè)字符串常量就可以了。
Result的值在xwork.xml配置文件里就是result標(biāo)簽里“name”的值,name="success"表示Action執(zhí)行成功,返回“success”就對(duì)應(yīng)此標(biāo)簽的配置,進(jìn)行視圖輸出。
“type”就是我們的Result Type,Result Type是一個(gè)類,它在Action執(zhí)行完成并返回Result之后,決定采用哪一種視圖技術(shù),將執(zhí)行結(jié)果展現(xiàn)給用戶。我們輸出的類型是:type="dispatcher",它對(duì)應(yīng)com.opensymphony.webwork.dispatcher.ServletDispatcherResult這個(gè)類,它將執(zhí)行結(jié)果通過javax.servlet.RequestDispatcher的forward()或include()方法調(diào)度到Jsp頁面展現(xiàn)。
我們可以自己開發(fā)Result Type,實(shí)現(xiàn)我們需要的視圖展現(xiàn)方式。Result Type必需要實(shí)現(xiàn)com.opensymphony.xwork..Result接口。在WebWork中,它已經(jīng)為我們提供了很多Result Type,實(shí)現(xiàn)了視圖部分對(duì)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()方法調(diào)度到頁面展現(xiàn),這樣的頁面一般是Jsp頁面。
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
例子:
<result name="success" type="dispatcher">
<param name="location">register-result.jsp</param>
</result>
也可以簡(jiǎn)單寫成這樣:
<result name="success" type="dispatcher">register-result.jsp</result>
Redirect:將響應(yīng)重定向到瀏覽器指定的位置,它將會(huì)導(dǎo)致Action執(zhí)行完成的數(shù)據(jù)丟失或不再可用。它在程序里是通過調(diào)用javax.servlet.http.HttpServletResponse.sendRedirect(String location)方法,將響應(yīng)定向到參數(shù)location指定的、新的url中。
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
例子
<result name="success" type="redirect">
<param name="location">foo.jsp</param>
<param name="parse">false</param>
</result>
Action Chaining:一種特殊的視圖結(jié)果,將Action執(zhí)行完之后鏈接到另一個(gè)Action中繼續(xù)執(zhí)行。新的Action使用上一個(gè)Action的上下文(ActionContext)。
參數(shù)(Parameters)
是否必需
描 述
actionName
是
將要被鏈接的Action名字
namespace
否
被鏈接的Action的命名空間(namespace),如果不設(shè)置,默認(rèn)的即是當(dāng)前的命名空間
例子:
<result name="success" type="chain">
<param name="actionName">bar</param>
<param name="namespace">/foo</param>
</result>
將要調(diào)用的Action如下:
<action name="bar" class="myPackage.barAction">
...
</action>
Velocity:它類似Jsp的執(zhí)行環(huán)境(使用JavaServlet容器),將Velocity模板轉(zhuǎn)化成數(shù)據(jù)流的形式,直接通過JavaServlet輸出。
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置(一般是.vm頁面)
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
例子:
<result name="success" type="velocity">
<param name="location">foo.vm</param>
</result>
FreeMarker:FreeMarker是一個(gè)純Java模板引擎;一個(gè)普通的基于模板生成文本的工具,它只能應(yīng)用在Web應(yīng)用環(huán)境中。
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
contentType
否
如果不指定,默認(rèn)的是"text/html"
例子:
<result name="success" type="freemarker">foo.ftl</result>
JasperReports:將Action執(zhí)行的結(jié)果通過JasperReports報(bào)表形式輸出,可以指定JasperReports支持的輸出格式(PDF、HTML、XLS、CSV、XML等),默認(rèn)是通過PDF格式輸出。
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
dataSource
是
它是Action的一個(gè)字段(通常是一個(gè)List),OGNL表達(dá)式被用來去value stack(OgnlValueStack)重新找回這個(gè)dataSource
format
否
報(bào)表生成的數(shù)據(jù)格式,默認(rèn)的是pdf
例子:
<result name="success" type="jasper">
<param name="location">foo.jasper</param>
<param name="dataSource">mySource</param>
<param name="format">CSV</param>
</result>
或者默認(rèn)的pdf格式
<result name="success" type="jasper">
<param name="location">foo.jasper</param>
<param name="dataSource">mySource</param>
</result>
XML/XSL:將結(jié)果轉(zhuǎn)換為xml輸出
參數(shù)(Parameters)
是否必需
描 述
location
是
執(zhí)行完成之后轉(zhuǎn)向的位置
parse
否
默認(rèn)的是“true”,如果設(shè)置為“false”,location參數(shù)將不會(huì)被OGNL表達(dá)式語言解析
例子:
<result name="success" type="xslt">foo.xslt</result>
式與言EL和OGNL
介紹
OGNL是Object-Graph Navigation Language的縮寫,它是一種功能強(qiáng)大的表達(dá)式語言(Expression Language,簡(jiǎn)稱為EL),通過它簡(jiǎn)單一致的表達(dá)式語法,可以存取對(duì)象的任意屬性,調(diào)用對(duì)象的方法,遍歷整個(gè)對(duì)象的結(jié)構(gòu)圖,實(shí)現(xiàn)字段類型轉(zhuǎn)化等功能。它使用相同的表達(dá)式去存取對(duì)象的屬性。
XWork遵循“不要重復(fù)地發(fā)明同一個(gè)輪子”的理論,它的表達(dá)式語言核心用的就是這個(gè)OGNL。我們先來看看一個(gè)簡(jiǎn)單的例子:
還記得我們用戶注冊(cè)的那個(gè)例子嗎?我們輸入框的name用到的名字就是OGNL的表達(dá)式,比如:用戶名的輸入框:“<input type="text" name="user.username">”,在用戶注冊(cè)成功之后我們要顯示用戶注冊(cè)的信息,用了“<ww:property value="user.username"/>”。Input輸入框里的“user.username”,它解析成Java語句為:getUser().setUsername();,property標(biāo)簽里的“user.username”解析為Java語句:getUser.getUsername();。
我們的兩個(gè)表達(dá)式都是相同的,但前一個(gè)保存對(duì)象屬性的值,后一個(gè)是取得對(duì)象屬性的值。表達(dá)式語言簡(jiǎn)單、易懂卻又功能強(qiáng)大,關(guān)于OGNL更多的介紹可以去
http://www.ognl.org,那里有很詳細(xì)的文檔。
OgnlValueStack
OGNL在框架中的應(yīng)用,最主要是支持我們的值堆棧(Value Stack)——OgnlValueStack,它主要的功能是通過表達(dá)式語言來存取對(duì)象的屬性。用戶界面輸入數(shù)據(jù),它會(huì)根據(jù)保存表達(dá)式將數(shù)據(jù)依次保存到它堆棧的對(duì)象中,業(yè)務(wù)操作完成,結(jié)果數(shù)據(jù)會(huì)通過表達(dá)式被獲取、輸出。
還記得我們用戶注冊(cè)的例子嗎?下面我們用一段程序來演示它向OgnlValueStack中保存、取得數(shù)據(jù)的步驟:
// 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、 創(chuàng)建我們的Action(RegisterAction)類的對(duì)象action,將action對(duì)象壓入堆棧valueStack中。在WebWrok中Action的創(chuàng)建、入棧是在DefaultActionInvocation構(gòu)造函數(shù)中進(jìn)行的,詳細(xì)介紹見:ServletDispatcher原理。
2、 通過表達(dá)式語言,調(diào)用堆棧對(duì)象的get()、set()方法,設(shè)置該對(duì)象的值。
public void setValue(String expr, Object value)
語句:valueStack.setValue("user.username","Moxie");
的作用等同于:action.getUser().setUsername("Moxie");
3、 通過表達(dá)式語言,去堆棧對(duì)象中查找我們前面保存的值,并在控制臺(tái)打印。valueStack.findValue("user.username")等同與語句:
action.getUser().getUsername()
最后控制臺(tái)打印的結(jié)果:
username = Moxie
CompoundRoot
在OgnlValueStack中,一個(gè)堆棧其實(shí)是一個(gè)List。查看OgnlValueStack你會(huì)發(fā)現(xiàn),堆棧就是com.opensymphony.xwork.util.CompoundRoot類的對(duì)象:
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);
}
}
我們通過表達(dá)式向堆棧對(duì)象操作時(shí),我們并不知道堆棧中有哪些對(duì)象。OgnlValueStack會(huì)根據(jù)堆棧由上向下的順序(先入棧在下面,最后入棧在最上面)依次去查找與表達(dá)式匹配的對(duì)象方法,找到即進(jìn)行相應(yīng)的存取操作。假設(shè)后面對(duì)象也有相同的方法,將不會(huì)被調(diào)用。
下面我們看一個(gè)對(duì)OgnlValueStack操作的程序,它主要演示了如何對(duì)Map對(duì)象的存取和OgnlValueStack堆棧的原理:
/*
* Created on 2004-6-15
* DemoGroupValueStack.java
*/
package example.register;
import com.opensymphony.xwork.util.OgnlValueStack;
/**
* @author moxie-qac
* achqian@yahoo.com.cn
*
*/
public class DemoGroupValueStack {
public void demoAction(){
DemoGroupAction action = new DemoGroupAction();
OgnlValueStack valueStack= new OgnlValueStack();
valueStack.push(action);
User zhao = new User();
zhao.setUsername("zhao");
zhao.setEmail("zhao@yahoo.com.cn");
User qian = new User();
qian.setUsername("qian");
qian.setEmail("qian@yahoo.com.cn");
valueStack.setValue("users[‘zhao‘]",zhao);
valueStack.setValue("users[‘qian‘]",qian);
System.out.println("users[‘zhao‘] = "+valueStack.findValue("users[‘zhao‘]"));
System.out.println("users[‘qian‘] = "+valueStack.findValue("users[‘qian‘]"));
System.out.println("users size = "+valueStack.findValue("users.size"));
System.out.println("allUserName[0] = "+valueStack.findValue("allUserName[0]"));
}
public void demoModels(){
User model_a = new User();
model_a.setUsername("model_a");
User model_b = new User();
model_b.setUsername("model_b");
User model_c = new User();
model_c.setUsername("model_c");
OgnlValueStack valueStack= new OgnlValueStack();
valueStack.push(model_a);
valueStack.push(model_b);
valueStack.push(model_c);
System.out.println("username = "+valueStack.findValue("username"));
System.out.println("[1].username = "+valueStack.findValue("[1].username"));
System.out.println("[0].toString = "+valueStack.findValue("[0]"));
System.out.println("[1].toString = "+valueStack.findValue("[1]"));
System.out.println("[2].toString = "+valueStack.findValue("[2]"));
}
public static void main(String[] args) {
DemoGroupValueStack demoValueStack = new DemoGroupValueStack();
demoValueStack.demoAction();
demoValueStack.demoModels();
}
}
/*
* Created on 2004-6-15
* DemoAction.java
*/
package example.register;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author moxie-qac
* achqian@yahoo.com.cn
*
*/
public class DemoGroupAction {
private Map users = new HashMap();
public Map getUsers(){
return this.users;
}
public List getAllUserName(){
return new ArrayList(users.keySet());
}
public String execute(){
//執(zhí)行業(yè)務(wù)操作
return null;
}
public String toString(){
return users.toString();
}
}
注意:1、Map屬性的存取,它的表達(dá)式語言如:users[‘zhao‘],注意它用’’來引用HashMap的key字符串。
2、demoModels()方法演示了OgnlValueStack中堆棧的原理,請(qǐng)?zhí)貏e注意它的[0].toString、[1].toString、[2].toString,它們依次調(diào)用堆棧中對(duì)象的toString()方法,并逐一的減少堆棧最上面的對(duì)象。
控制臺(tái)輸出的結(jié)果如下:
users[‘zhao‘] = username=zhao;password=null;email=zhao@yahoo.com.cn;age=0
users[‘qian‘] = username=qian;password=null;email=qian@yahoo.com.cn;age=0
users size = 2
allUserName[0] = qian
username = model_c
[1].username = model_b
[0].toString = [username=model_c;password=null;email=null;age=0, username=model_b;password=null;email=null;age=0, username=model_a;password=null;email=null;age=0]
[1].toString = [username=model_b;password=null;email=null;age=0, username=model_a;password=null;email=null;age=0]
[2].toString = [username=model_a;password=null;email=null;age=0]
攔截器)框架
Interceptor(攔截器)將Action共用的行為獨(dú)立出來,在Action執(zhí)行前后運(yùn)行。這也就是我們所說的AOP(Aspect Oriented Programming,面向切面編程),它是分散關(guān)注的編程方法,它將通用需求功能從不相關(guān)類之中分離出來;同時(shí),能夠使得很多類共享一個(gè)行為,一旦行為發(fā)生變化,不必修改很多類,只要修改這個(gè)行為就可以。
Interceptor將很多功能從我們的Action中獨(dú)立出來,大量減少了我們Action的代碼,獨(dú)立出來的行為具有很好的重用性。XWork、WebWork的許多功能都是有Interceptor實(shí)現(xiàn),可以在配置文件中組裝Action用到的Interceptor,它會(huì)按照你指定的順序,在Action執(zhí)行前后運(yùn)行。Interceptor在框架中的應(yīng)用如下圖所示:
當(dāng)你提交對(duì)Aciton(默認(rèn)是.action結(jié)尾的Url)的請(qǐng)求時(shí),ServletDispatcher會(huì)根據(jù)你的請(qǐng)求,去調(diào)度并執(zhí)行相應(yīng)的Action。在Action執(zhí)行之前,調(diào)用被 Interceptor截取,Interceptor在Action執(zhí)行前后運(yùn)行。
我們?cè)谟脩糇?cè)的例子中就使用了取得Request請(qǐng)求參數(shù)的攔截器,配置文件中<interceptor-ref name="params"/>將攔截器params組裝到RegisterAction中?!皃arams”在我們的webwork-default.xml配置文件中有定義,webwork-default.xml中攔截器的定義如下:
<interceptors>
<interceptor name="timer" class="com.opensymphony.xwork.interceptor.TimerInterceptor"/>
<interceptor name="logger" class="com.opensymphony.xwork.interceptor.LoggingInterceptor"/>
<interceptor name="chain" class="com.opensymphony.xwork.interceptor.ChainingInterceptor"/>
<interceptor name="static-params" class="com.opensymphony.xwork.interceptor.StaticParametersInterceptor"/>
<interceptor name="params" class="com.opensymphony.xwork.interceptor.ParametersInterceptor"/>
<interceptor name="model-driven" class="com.opensymphony.xwork.interceptor.ModelDrivenInterceptor"/>
<interceptor name="component" class="com.opensymphony.xwork.interceptor.component.ComponentInterceptor"/>
<interceptor name="token" class="com.opensymphony.webwork.interceptor.TokenInterceptor"/>
<interceptor name="token-session" class="com.opensymphony.webwork.interceptor.TokenSessionStoreInterceptor"/>
<interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>
<interceptor name="workflow" class="com.opensymphony.xwork.interceptor.DefaultWorkflowInterceptor"/>
<interceptor name="servlet-config" class="com.opensymphony.webwork.interceptor.ServletConfigInterceptor"/>
<interceptor name="prepare" class="com.opensymphony.xwork.interceptor.PrepareInterceptor"/>
<interceptor name="conversionError" class="com.opensymphony.webwork.interceptor.WebWorkConversionErrorInterceptor"/>
<interceptor-stack name="defaultStack">
<interceptor-ref name="static-params"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
<interceptor-stack name="validationWorkflowStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="validation"/>
<interceptor-ref name="workflow"/>
</interceptor-stack>
</interceptors>
這些都時(shí)有框架提供的默認(rèn)的Interceptor,下面我來看看Interceptor使用的步驟:
1、 創(chuàng)建一個(gè)自己需要的Interceptor類,它必需實(shí)現(xiàn)
com.opensymphony.xwork.interceptor.Interceptor接口,具體的開發(fā)見下面的Interceptor的原理。
2、 在配置文件(xwork..xml)中申明這個(gè)Interceptor類,它放在標(biāo)簽<interceptor />中,同是<interceptor />標(biāo)簽嵌入在<interceptors />標(biāo)簽內(nèi)部。
3、 創(chuàng)建Interceptor棧,使用標(biāo)簽:<interceptor-stack />,讓一組Interceptor可以按次序調(diào)用。(可選)
4、 指定Action所要用到的Interceptor(前面申明過的),可以用<interceptor-ref />或<default-interceptor-ref />標(biāo)簽。前面的標(biāo)簽指定某個(gè)Action所用到的Interceptor,如果Action沒有被用<interceptor-ref />指定Interceptor,它將使用<default-interceptor-ref />指定的Interceptor。
框架中給我們提供了很多實(shí)用的Interceptor,它的定義上面已經(jīng)給出,它的具體功能如下:
l timer:記錄Action執(zhí)行的時(shí)間,并做為日志信息輸出;
l logger:在日志信息中輸出要執(zhí)行的Action信息;
l chain:將前一個(gè)執(zhí)行結(jié)束的Action屬性設(shè)置到當(dāng)前的Action中。它被用在ResultType為“chain”指定結(jié)果的Action中,該結(jié)果Action對(duì)象會(huì)從OgnlValueStack中獲得前一個(gè)Action對(duì)應(yīng)的屬性,它實(shí)現(xiàn)Action鏈之間的數(shù)據(jù)傳遞;
l static-params:將xwork.xml配置文件里定義的Action參數(shù),設(shè)置到對(duì)應(yīng)的Action中。Action參數(shù)使用<param />標(biāo)簽,是<action />標(biāo)簽的直接子元素。我們這里定義的Action類必需實(shí)現(xiàn)com.opensymphony.xwork.config.entities. Parameterizable接口;
l params:將Request請(qǐng)求的參數(shù)設(shè)置到相應(yīng)Action對(duì)象的屬性中,用戶注冊(cè)例子用到過這個(gè)攔截器;
l model-driven:如果Action實(shí)現(xiàn)ModelDriven接口,它將getModel()取得的模型對(duì)象存入OgnlValueStack中;
l component:激活組件功能支持,讓注冊(cè)過的組件在當(dāng)前Action中可用,即為Action提供IoC(依賴倒轉(zhuǎn)控制)框架的支持;
l token:核對(duì)當(dāng)前Action請(qǐng)求(request)的有效標(biāo)識(shí),防止重復(fù)提交Action請(qǐng)求(request)。
l token-session:功能同上,但是當(dāng)提交無效的Action請(qǐng)求標(biāo)識(shí)時(shí),它會(huì)將請(qǐng)求數(shù)據(jù)保存到session中。
l validation:實(shí)現(xiàn)使用xml配置文件({Action}-validation.xml)對(duì)Action屬性值進(jìn)行驗(yàn)證,詳細(xì)請(qǐng)看后面介紹的驗(yàn)證框架。
l workflow:調(diào)用Action類的驗(yàn)證功能,假設(shè)Action使用ValidationAware實(shí)現(xiàn)驗(yàn)證(ActionSupport提供此功能),如果驗(yàn)證沒有通過,workflow會(huì)將請(qǐng)求返回到input視圖(Action的<result />中定義的)。
l servlet-config:提供Action直接對(duì)HttpServletRequest或HttpServletResponse等JavaServlet api的訪問,Action要實(shí)現(xiàn)相應(yīng)的接口,例如:ServletRequestAware或ServletResponseAware等。如果必需要提供對(duì)JavaServlet api的訪問,我們建議使用ServletActionContext,在前面ActionContext章節(jié)中有介紹。
l prepare:在Action執(zhí)行之前調(diào)用Action的prepare()方法,這個(gè)方法是用來準(zhǔn)備Action執(zhí)行之前要做的工作。它要求我們的Action必需實(shí)現(xiàn)com.opensymphony.xwork. Preparable接口
l conversionError:用來處理框架進(jìn)行類型轉(zhuǎn)化(Type Conversion)時(shí)的出錯(cuò)信息。它將存儲(chǔ)在ActionContext中的類型轉(zhuǎn)化(Type Conversion)錯(cuò)誤信息轉(zhuǎn)化成相應(yīng)的Action字段的錯(cuò)誤信息,保存在堆棧中。根據(jù)需要,可以將這些錯(cuò)誤信息在視圖中顯示出來。
的原理
下面我們來看看Interceptor是如何實(shí)現(xiàn)在Action執(zhí)行前后調(diào)用的:
Action和Interceptor在框架中的執(zhí)行,是由ActionInvocation對(duì)象調(diào)用的。它是用方法:String invoke() throws Exception;來實(shí)現(xiàn)的,它首先會(huì)依次調(diào)用Action對(duì)應(yīng)的Interceptor,執(zhí)行完成所有的Interceptor之后,再去調(diào)用Action的方法,代碼如下:
if (interceptors.hasNext()) {
Interceptor interceptor = (Interceptor) interceptors.next();
resultCode = interceptor.intercept(this);
} else {
if (proxy.getConfig().getMethodName() == null) {
resultCode = getAction().execute();
} else {
resultCode = invokeAction(getAction(), proxy.getConfig());
}
}
它會(huì)在攔截器棧中遍歷Interceptor,調(diào)用Interceptor的方法:
String intercept(ActionInvocation invocation) throws Exception;。
我們一直都提到,Interceptor是在Action前后執(zhí)行,可是從上面的代碼我們看到的卻是執(zhí)行完所有Interceptor的intercept()方法之后再去調(diào)用我們的Action?!霸贏ction前后執(zhí)行”是如何實(shí)現(xiàn)的呢?我們來看看抽象類AroundInterceptor的intercept()實(shí)現(xiàn):
public String intercept(ActionInvocation invocation) throws Exception {
String result = null;
before(invocation);
result = invocation.invoke();
after(invocation, result);
return result;
}
原來在intercept()方法又對(duì)ActionInvocation的invoke()方法進(jìn)行遞歸調(diào)用,ActionInvocation循環(huán)嵌套在intercept()中,一直到語句result = invocation.invoke();執(zhí)行結(jié)束,即:Action執(zhí)行完并返回結(jié)果result,這時(shí)Interceptor對(duì)象會(huì)按照剛開始執(zhí)行的逆向順序依次執(zhí)行結(jié)束。這樣before()方法將在Action執(zhí)行前調(diào)用,after()方法在Action執(zhí)行之后運(yùn)行。
WebWork提供了在Action執(zhí)行之前,對(duì)輸入數(shù)據(jù)的驗(yàn)證功能,它使用了其核心XWork的驗(yàn)證框架。提供了如下功能:
1、 可配置的驗(yàn)證文件。它的驗(yàn)證文件是一個(gè)獨(dú)立的XML配置文件,對(duì)驗(yàn)證的添加、修改只需更改配置文件,無需編譯任何的Class。
2、 驗(yàn)證文件和被驗(yàn)證的對(duì)象完全解藕。驗(yàn)證對(duì)象是普通的JavaBean就可以了(可以是FormBean、域?qū)ο蟮龋?,它們不需?shí)現(xiàn)任何額外的方法或繼承額外的類。
3、 多種不同的驗(yàn)證方式。因?yàn)樗?yàn)證功能是可以繼承的,所以可以用多種不同的方式指定驗(yàn)證文件,比如:通過父類的Action、通過Action、通過Action的方法、通過Action所使用的對(duì)象,等等。
4、 強(qiáng)大的表達(dá)式驗(yàn)證。它使用了OGNL的表達(dá)式語言,提供強(qiáng)大的表達(dá)式驗(yàn)證功能。
5、 同時(shí)支持服務(wù)器端和客戶端驗(yàn)證。
下面我們來看看如何為用戶注冊(cè)添加驗(yàn)證功能:
1、 注冊(cè)我們的驗(yàn)證類型
WebWork為不同的驗(yàn)證要求提供不同的驗(yàn)證類型。一個(gè)驗(yàn)證類型,一般是有一個(gè)類來提供。這個(gè)類必須實(shí)現(xiàn)接口:com.opensymphony.xwork.validator.Validator,但我們?cè)趯懽约旱尿?yàn)證類型時(shí),無需直接實(shí)現(xiàn)Validator接口,它有抽象類可供直接使用如ValidatorSupport、FieldValidatorSupport等。
驗(yàn)證類型在使用之前,必須要在ValidatorFactory(com.opensymphony.xwork.validator. ValidatorFactory)中注冊(cè)??梢杂卸N方法實(shí)現(xiàn)驗(yàn)證類型的注冊(cè)。一、寫程序代碼進(jìn)行注冊(cè),它使用ValidatorFactory類的靜態(tài)方法:registerValidator(String name, String className)。二、使用配置文件validators.xml進(jìn)行注冊(cè),要求把文件validators.xml放到ClassPath的跟目錄中(/WEB-INF/classes)。但在實(shí)際開發(fā)中,一般都使用第二中注冊(cè)方法。我們的驗(yàn)證類型注冊(cè)如下:
<validators>
<validator name="required" class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/>
<validator name="requiredstring" class="com.opensymphony.xwork.validator.validators.RequiredStringValidator"/>
<validator name="int" class="com.opensymphony.xwork.validator.validators.IntRangeFieldValidator"/>
<validator name="date" class="com.opensymphony.xwork.validator.validators.DateRangeFieldValidator"/>
<validator name="expression" class="com.opensymphony.xwork.validator.validators.ExpressionValidator"/>
<validator name="fieldexpression" class="com.opensymphony.xwork.validator.validators.FieldExpressionValidator"/>
<validator name="email" class="com.opensymphony.xwork.validator.validators.EmailValidator"/>
<validator name="url" class="com.opensymphony.xwork.validator.validators.URLValidator"/>
<validator name="visitor" class="com.opensymphony.xwork.validator.validators.VisitorFieldValidator"/>
<validator name="conversion" class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/>
<validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>
</validators>
注冊(cè)驗(yàn)證類型的配置文件非常簡(jiǎn)單。它使用標(biāo)簽<validator>提供名-值對(duì)的形式注冊(cè)。這樣我們的驗(yàn)證文件就可以直接引用它的名字。
2、 開啟Action的驗(yàn)證功能
如果Action要使用驗(yàn)證框架的驗(yàn)證功能,它必須在配置文件中指定攔截器“validation”,它的定義如下:
<interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>。
我們的驗(yàn)證文件必須以ActionName-validation.xml格式命名,它必須被放置到與這個(gè)Action相同的包中。你也可以為這個(gè)Action通過別名的方式指定驗(yàn)證文件,它的命名格式為:ActionName-aliasname-validation.xml。“ActionName ”是我們Action的類名;“aliasname”是我們?cè)谂渲梦募▁work.xml)中定義這個(gè)Action所用到的名稱。這樣,同一個(gè)Action類,在配置文件中的不同定義就可以對(duì)應(yīng)不同的驗(yàn)證文件。驗(yàn)證框架也會(huì)根據(jù)Action的繼承結(jié)構(gòu)去查找Action的父類驗(yàn)證文件,如果找到它會(huì)去執(zhí)行這個(gè)父類的驗(yàn)證。
3、 實(shí)現(xiàn)我們的驗(yàn)證文件:RegisterActionSupport-validation.xml
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
<field name="user.username">
<field-validator type="requiredstring">
<message>You must enter a value for username.</message>
</field-validator>
</field>
<field name="user.password">
<field-validator type="requiredstring">
<message>You must enter a value for password.</message>
</field-validator>
<field-validator type="fieldexpression">
<param name="expression">user.password == verifyPassword</param>
<message>Passwords don‘t match.</message>
</field-validator>
</field>
<field name="user.email">
<field-validator type="email">
<message>You must enter a valid email.</message>
</field-validator>
</field>
<field name="user.age">
<field-validator type="int">
<param name="min">6</param>
<param name="max">100</param>
<message>Age must be between ${min} and ${max}, current value is ${user.age}.</message>
</field-validator>
</field>
</validators>
說明:
1)、<field>標(biāo)簽代表一個(gè)字段,它的屬性“name”和頁面輸入框的“name”屬性必需完全一致,其實(shí)它也就是我們的表達(dá)式語言。
2)、<field-validator>標(biāo)簽定義我們的驗(yàn)證規(guī)則,type屬性的值就是就是我們前面定義的驗(yàn)證類型。
3)、驗(yàn)證文件中,字段的數(shù)據(jù)是通過表達(dá)式語言從我們的值堆棧(OgnlValueStack)中取得,一般是Action或Model對(duì)象。例如:我們的字段“user.age”,它會(huì)通過Action的getUser().getAge()來取得用戶輸入的年齡,再來根據(jù)驗(yàn)證的類型“int”和最大值最小值的參數(shù)來判斷輸入的數(shù)據(jù)是否能通過驗(yàn)證。
4)、不管驗(yàn)證是否通過,我們的Action都會(huì)執(zhí)行,但如果驗(yàn)證沒有通過,它不會(huì)調(diào)用Action的execute()方法。
4、 顯示Action的驗(yàn)證錯(cuò)誤信息
如果用戶輸入的數(shù)據(jù)驗(yàn)證沒有通過,我們需重新返回輸入頁面,并給出錯(cuò)誤信息提示。攔截器?!皏alidationWorkflowStack”為我們實(shí)現(xiàn)了這個(gè)功能。它首先驗(yàn)證用戶輸入的數(shù)據(jù),如果驗(yàn)證沒有通過將不執(zhí)行我們Action的execute()方法,而是將請(qǐng)求重新返回到輸入頁面。
我們的xwork.xml配置文件如下:
<action name="registerSupport" class="example.register.RegisterActionSupport" >
<result name="success" type="dispatcher">
<param name="location">/register-result.jsp</param>
</result>
<result name="input" type="dispatcher">
<param name="location">/registerSupport.jsp</param>
</result>
<interceptor-ref name="validationWorkflowStack"/>
</action>
通過接口ValidationAware,我們可以獲得類級(jí)別或字段級(jí)別的驗(yàn)證錯(cuò)誤信息,這個(gè)錯(cuò)誤信息也就是我們驗(yàn)證文件中<message>標(biāo)簽里的數(shù)據(jù)。ActionSupport類已實(shí)現(xiàn)了此接口,這樣在應(yīng)用中我們的Action只要繼承ActionSupport類就可以了。RegisterActionSupport.java代碼如下:
package example.register;
import com.opensymphony.xwork.ActionSupport;
/**
* @author moxie-qac
* achqian@yahoo.com.cn
*
*/
public class RegisterActionSupport extends ActionSupport {
private User user= new User();
private String verifyPassword;
public User getUser(){
return this.user;
}
public String execute(){
//在這里調(diào)用用戶注冊(cè)的業(yè)務(wù)邏輯,比如:將注冊(cè)信息存儲(chǔ)到數(shù)據(jù)庫
return SUCCESS;
}
public String getVerifyPassword(){
return this.verifyPassword;
}
public void setVerifyPassword(String verPassword){
this.verifyPassword = verPassword;
}
}
我們WebWork的UI標(biāo)簽庫直接提供了驗(yàn)證錯(cuò)誤信息顯示功能。如果字段級(jí)別的驗(yàn)證沒有通過,它會(huì)在輸入框上方顯示驗(yàn)證文件定義的錯(cuò)誤提示信息。我們將用戶輸入的頁面更改如下:
registerSupport.jsp
<%@ taglib uri="webwork" prefix="ww" %>
<html>
<head><title>Register Example</title></head>
<body>
<table border=0 width=97%>
<tr><td align="left">
<ww:form name="‘test‘" action="‘/example/registerSupport.action‘" method="‘POST‘">
<ww:textfield label="‘Username‘" name="‘user.username‘" required="true"/>
<ww:textfield label="‘Password‘" name="‘user.password‘" required="true"/>
<ww:textfield label="‘VerifyPassword‘" name="‘verifyPassword‘" required="true"/>
<ww:textfield label="‘Email‘" name="‘user.email‘" required="true"/>
<ww:textfield label="‘Age‘" name="‘user.age‘" required="true"/>
<ww:submit value="‘Submit‘"/>
</ww:form>
</td></tr>
</table>
</body>
</html>
我們上面的例子使用的是服務(wù)器端驗(yàn)證。WebWork也為我們提供了方便的客戶端驗(yàn)證。它將驗(yàn)證自動(dòng)生成JavaScript腳本。如果要使用客戶端驗(yàn)證只需改變相應(yīng)的驗(yàn)證類型就可以了(輸入頁面的表單必需使用<ww:form>標(biāo)簽,并設(shè)置屬性“validate="true"”)。具體的驗(yàn)證類型可以在WebWork的包c(diǎn)om.opensymphony.webwork.validators中找到。
配置詳述
XWork配置文件是以“xwork”命名的.xml文件,它必需放到類路徑(classPath)的根目錄, Web應(yīng)用一般放在classes目錄中,它需要遵守DTD的規(guī)范(現(xiàn)在是xwork-1.0.dtd)。這個(gè)文件定義了我們的Action,Interceptor,Result的配置和相互之間的映射。下面我們看看用戶注冊(cè)的完整XWork配置文件:
<!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="example" extends="webwork-default">
<action name="register" class="example.register.RegisterAction" >
<result name="success" type="dispatcher">
<param name="location">/register-result.jsp</param>
</result>
<interceptor-ref name="params"/>
</action>
<action name="registersupport" class="example.register.RegisterActionSupport" >
<result name="success" type="dispatcher">
<param name="location">/register-result.jsp</param>
</result>
<result name="input" type="dispatcher">
<param name="location">/registerSupport.jsp</param>
</result>
<interceptor-ref name="validationWorkflowStack"/>
</action>
</package>
</xwork>
文件的標(biāo)簽元素
Xwork:xwork配置文件的所有內(nèi)容,都是定義在<xwork>標(biāo)簽中,它的直接子標(biāo)簽有<package>和<include>。
Package:我們的Action,Interceptor,Result都是在此標(biāo)簽中定義。<package>標(biāo)簽有一個(gè)必需的屬性“name”,它用來標(biāo)識(shí)唯一的一個(gè)package。屬性“extends”是可選的,它用來繼承前面定義的一個(gè)或一個(gè)以上package配置信息,包括所有的interceptor、interceptor-stack和action的配置信息。注意,配置文件按文檔的順序,由上向下執(zhí)行,因此,用“extends”引用的package必需在引用之前定義。屬性“sbstract”是可選的,它用來設(shè)置package為抽象的package,它可以被繼承同時(shí)它的Action配置信息在運(yùn)行時(shí)將不可見。
屬性namespace也是可選的,它用來分隔不同package定義的action,讓這些action處于不同的命名空間(namespaces)。這樣,我們不同的package可以有相同的action命名,因?yàn)榭梢酝ㄟ^命名空間來區(qū)分。如果不指定namespace,默認(rèn)的是空字符串。命名空間也可以被用在安全控制方面,它可以根據(jù)不同的命名空間指定不同的訪問權(quán)限。
屬 性
是否必需
描 述
name
是
用來標(biāo)識(shí)package的名稱
extends
否
繼承它所擴(kuò)展的package配置信息
namespace
否
指定package的命名空間,默認(rèn)是””
abstract
否
聲明package是抽象的
Result-type:用來定義輸出結(jié)果類型的Class,它用簡(jiǎn)單的名-值對(duì)來定義。當(dāng)然,我們自己寫的輸出結(jié)果類型也必需在這里定義。例如:
<result-type name="dispatcher" class="com.opensymphony.webwork.dispatcher.ServletDispatcherResult" default="true"/>,default="true"表示如果在Action的result中不指定result-type,就使用這個(gè)默認(rèn)的result-type。
Interceptors:它是一個(gè)簡(jiǎn)單的<interceptors> <interceptors/>標(biāo)簽,我們的interceptor和interceptor-stack都在此標(biāo)簽內(nèi)定義。
Interceptor:當(dāng)然,就是用來定義我們的攔截器。它的定義非常簡(jiǎn)單,名-值對(duì)的形式。例如:<interceptor name="timer" class="com.opensymphony.xwork.interceptor.TimerInterceptor"/>。在action中,可以通過<interceptor-ref />來直接引用前面定義了的攔截器。
Interceptor-stack:用來將上面定義的interceptor組織成堆棧的形式,這樣我們就可以創(chuàng)建一組標(biāo)準(zhǔn)的interceptor,讓它按照順序執(zhí)行。在我們的Action中直接引用這個(gè)interceptor堆棧就可以了,不用逐個(gè)interceptor去引用。
例如:
<interceptor-stack name="validationWorkflowStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="validation"/>
<interceptor-ref name="workflow"/>
</interceptor-stack>
Interceptor Param:我們的interceptor是在ActionConfig級(jí)別被實(shí)例化和存儲(chǔ)的,也就是說一個(gè)Action引用的每個(gè)interceptor都會(huì)有相應(yīng)的實(shí)例。這樣,我們?cè)诙x和引用interceptor的時(shí)候都可以為它設(shè)置相應(yīng)的參數(shù)值。例如:
<interceptor name="test" class="com.opensymphony.xwork.TestInterceptor">
<param name="foo">expectedFoo</param>
</interceptor>
在Action或Interceptor-stack中引用時(shí)也可以設(shè)置參數(shù),例如:
<interceptor-ref name="test">
<param name="expectedFoo">expectedFoo</param>
</interceptor-ref>
注意:在Action引用的時(shí)候,如果引用的是Interceptor-stack,則不允許設(shè)置參數(shù),否則會(huì)報(bào)錯(cuò)。
Global-results:它允許我們定義全局的輸出結(jié)果(global result),比如登陸頁面、操作錯(cuò)誤處理頁面。只要繼承它所在的package,這些輸出結(jié)果都是可見的。
例如:
<global-results>
<result name="login" type="dispatcher">
<param name="location">/login.jsp</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/error.jsp</param>
</result>
</global-results>
如果我們的Action執(zhí)行完返回“l(fā)ogin”,它將調(diào)用上面的這個(gè)輸出結(jié)果,將輸出派遣到根目錄下的login.jsp頁面。
Action:用來配置Action的名稱(name)和它對(duì)應(yīng)的Class。我們將通過這個(gè)Action的名稱和它所在package的namespace去配置文件中取得這個(gè)Action的配置信息。它可以通過<param>來設(shè)置參數(shù),Action在執(zhí)行的時(shí)候會(huì)取得配置文件里設(shè)置的參數(shù)(通過攔截器StaticParametersInterceptor)。
Action可以配置一個(gè)或多個(gè)輸出結(jié)果(result)。一個(gè)輸出結(jié)果的名稱,對(duì)應(yīng)于Action執(zhí)行完成返回的字符串。<result>標(biāo)簽的type屬性,對(duì)應(yīng)我們前面定義過的result-type,說明reslut的類型。例如:
<action name="register" class="example.register.RegisterAction" >
<result name="success" type="dispatcher">
<param name="location">/register-result.jsp</param>
</result>
<interceptor-ref name="params"/>
</action>
當(dāng)然,我們的Action用到的攔截器也是在這里配置的,通過<interceptor-ref>標(biāo)簽,屬性“name”的值,對(duì)應(yīng)前面定義的interceptor或interceptor-stack。如果Action中沒有用<interceptor-ref>標(biāo)簽指定攔截器,它將使用默認(rèn)的<default-interceptor-ref>標(biāo)簽定義的攔截器。
Include:xwork..xml文件可以被分成好幾個(gè)不同的文件,xwork..xml通過<include>標(biāo)簽引用被包含的文件,例如:<include file="webwork-default.xml"/>。被包含的文件必需是package標(biāo)簽里的內(nèi)容,我們看看<include>標(biāo)簽在配置文件里的位置就知道了。如果要繼承被包含文件的package,我們必需將<include>標(biāo)簽放在其上面,因?yàn)榕渲梦募前凑沼缮隙碌捻樞蚪馕龅摹?div style="height:15px;">
WebWork功能非常強(qiáng)大,除了上面介紹的以外,它還有很好的國際化支持功能,IoC(Inversion of control,依賴倒裝控制)框架支持;同時(shí),它也可以很好的與其它的開源項(xiàng)目集成,如:Sitemesh、Spring、Pico、Hibernate、JUnit、Quartz等。
“最好的文檔就是代碼”,WebWork代碼可讀性非常好,特別是2.1版本加了很多詳盡的注釋,在此向讀者強(qiáng)烈推薦,如果想更深入了解WebWork,建議多看它的代碼文檔。
到此,您已經(jīng)了解了WebWork的所有特性。它確實(shí)是一個(gè)非常優(yōu)秀的開源J2EE Web框架,同時(shí)我并不否定其它的框架,比如Struts,Tapestry,Maverick等,既然存在,它就一定有著自身存在價(jià)值和理由。
這么多的Web框架,有很多朋友在面臨選擇的時(shí)候也許會(huì)非常矛盾,不知應(yīng)該如何抉擇。在這,我的建議:關(guān)于開源Web框架的選擇,應(yīng)該根據(jù)團(tuán)隊(duì)的整體技術(shù)能力和要實(shí)施的項(xiàng)目來共同決定。關(guān)于是否要在項(xiàng)目中使用WebWork,假如你們已經(jīng)在團(tuán)隊(duì)中使用類似Struts這樣的J2EE框架,開發(fā)人員都已熟悉并有了很多技術(shù)和項(xiàng)目經(jīng)驗(yàn)的積累,那我建議你們暫時(shí)不要去使用WebWork,但我強(qiáng)烈建議找一個(gè)有代表性的模塊,將他們嘗試用WebWork改寫,我想,下個(gè)項(xiàng)目,也許你們就會(huì)改變注意,考慮使用WebWork。但,如果你們正在為具體選擇哪種Web框架而發(fā)愁,我相信WebWork一定是您最佳的選擇。
我是從WebWork開始認(rèn)識(shí)Opensymphony(http://www.opensymphony.com)的,它是一個(gè)很好提供開源項(xiàng)目的組織。同Jakarta相比,這里的組件(Component)更多的是精致小巧的設(shè)計(jì),它們尤以簡(jiǎn)單易用和可插拔的靈活性見長(zhǎng)。除了我們這里介紹的WebWork和Xwork,下面我們將簡(jiǎn)單介紹其他的一些組件:
OSWorkFlow:工作流引擎。它的流程定義靈活清晰,工作流引擎支持多種持久方式(MemoryStore ,SerializableStore, JDBCStore, OfbizStore, and EJBStore,HibernateStore等),具有極強(qiáng)的可擴(kuò)展性。它提供了強(qiáng)大的腳本支持(BeanShell、BSF等),多樣化的function,function可以直接使用普通java類函數(shù)、Xwork的Action、JMS、EJB、腳本等。它還提供了一個(gè)基于JGraph的流程設(shè)計(jì)器。
Quartz:它是一個(gè)實(shí)現(xiàn)任務(wù)定時(shí)調(diào)度的框架,原先是一個(gè)獨(dú)立的project,后來并入OpenSymphony。它是一個(gè)非常輕量級(jí)的,并具有高度的可升級(jí)性,提供了簡(jiǎn)單易用的接口。它提供了強(qiáng)大的任務(wù)調(diào)度運(yùn)行方式,可以獨(dú)立運(yùn)行、可以作為EJB部署于容器中、本身支持cluster,等等。
SiteMesh:它主要用來對(duì)Web頁面的布局管理,并且致力為很多頁面組成的大型網(wǎng)站提供提供統(tǒng)一的風(fēng)格、導(dǎo)航和布局功能。
它通過filter截取request和response,并給原始的頁面加入一定的裝飾(Decorator)(可能為header,footer...),然后把結(jié)果返回給客戶端,并且被裝飾的原始頁面并不知道SiteMesh的裝飾,這也就達(dá)到了解耦的目的。
OSCache:J2EE Caching機(jī)制。它主要用于JSP Caching、Request Caching、General-Purpose Cache三個(gè)方面。在JSP Caching、Request Caching方面,OSCache能夠解決動(dòng)態(tài)網(wǎng)站的基本問題:緩存動(dòng)態(tài)內(nèi)容、緩存二進(jìn)制內(nèi)容、錯(cuò)誤包容。在General-Purpose Cache方面,在Java應(yīng)用中通過調(diào)用OSCache的API來緩存任意的Java對(duì)象,hibernate 2.0開始對(duì)其也有支持。
OSCache標(biāo)記庫是一種開創(chuàng)性的JSP定制標(biāo)記應(yīng)用,提供了在現(xiàn)有JSP頁面之內(nèi)實(shí)現(xiàn)快速內(nèi)存緩沖的功能。雖然已經(jīng)有一些供應(yīng)商在提供各種形式的緩存產(chǎn)品,但是,它們都屬于面向特定供應(yīng)商的產(chǎn)品。OSCache能夠在任何JSP 1.2兼容的服務(wù)器上運(yùn)行,它不僅能夠?yàn)樗杏脩艟彌_現(xiàn)有JSP代碼塊,而且能夠以用戶為單位進(jìn)行緩沖。OSCache還包含一些提高可伸縮性的高級(jí)特性,比如:緩沖到磁盤,可編程的緩沖刷新,異??刂疲鹊?。
PropertySet:管理屬性(Property)的好工具,它提供一個(gè)抽象方法來向一個(gè)持久性存儲(chǔ)源中動(dòng)態(tài)保存和取回類型化的屬性數(shù)據(jù)。支持多種持久化方式,例如:XML, EJB, Ofbiz, JDBC, Castor JDO,Memory等,同時(shí)也提供了一個(gè)簡(jiǎn)單的API來根據(jù)你的需要寫你自己定制的PropertySets。
Clickstream:它是一個(gè)JavaServlet過濾器,用來跟蹤用戶請(qǐng)求(比如:點(diǎn)擊)和請(qǐng)求隊(duì)列(比如:點(diǎn)擊流)以向網(wǎng)絡(luò)管理員顯示誰在她的網(wǎng)站上以及每個(gè)用戶正在訪問那個(gè)頁面。
在Struts里面,每一個(gè)Action類必需要繼承一個(gè)抽象的類org.apache.struts.action.Action。這個(gè)在Java編程中會(huì)引來一些問題,就是關(guān)于多種繼承的問題。
WebWork的Action類僅需要實(shí)現(xiàn)接口com.opensymphony.xwork.Action,也可以實(shí)現(xiàn)其它的接口來實(shí)現(xiàn)更多的功能,譬如:validate(驗(yàn)證),localware(國際化)等。當(dāng)然,它也提供了一個(gè)類ActionSupport集成了上面的所有功能,我們?cè)陂_發(fā)中可以根據(jù)需要選擇。
Struts 的Action必需是thread-safe方式,它僅僅允許一個(gè)實(shí)例去處理所有的請(qǐng)求。所以action用到的所有的資源都必需統(tǒng)一同步,這個(gè)就引起了線程安全的問題。
在WebWork中,每個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)Action,因此沒有線程的安全問題。實(shí)際上Servlet容器對(duì)每個(gè)請(qǐng)求也產(chǎn)生多個(gè)對(duì)象,它也沒有證明對(duì)性能和垃圾回收產(chǎn)生太多的影響。
WebWork的Action不用依賴Web層和其它的容器。它可以通過ActionContext,直接去訪問Request和Response,但這個(gè)是可選的,只有在必需的請(qǐng)求下使用。
Struts的每個(gè)Action都同Web層耦合在一起,這樣它的測(cè)試依賴于Web容器,單元測(cè)試也很難實(shí)現(xiàn)。不過有一個(gè)Junit的擴(kuò)展工具Struts TestCase可以實(shí)現(xiàn)它的單元測(cè)試。
Webwork的action能夠通過賦予一定的屬性,就可以執(zhí)行單元測(cè)試。同時(shí)也可以使用一個(gè)mock的實(shí)例去測(cè)試,而不是通過啟動(dòng)web容器來進(jìn)行測(cè)試。
Struts要求有FormBean對(duì)應(yīng)每一個(gè)表單,而且FormBean必需繼承抽象類ActionForm。而使用DynaBeans實(shí)際上沒有太大的意義。不能夠很好的處理現(xiàn)有的模型。
Webwork 能夠動(dòng)態(tài)的收集web的數(shù)據(jù)然后再賦值給bean。它也可以使用FormBean的形式,F(xiàn)ormBean可以是普通的DTO和域?qū)ο?,它不用重新根?jù)域?qū)ο髞砩尚碌腇ormBean,也不需繼承抽象類ActionForm。
Struts集成了JSTL,所以它主要使用JSTL的表達(dá)式語言來獲取數(shù)據(jù)??墒荍STL的表達(dá)式語言在Collection和索引屬性方面處理顯得很弱。
WebWork的表達(dá)式語言使用了功能強(qiáng)大的OGNL。它使用OGNL建立一個(gè)OgnlValueStack來搜索數(shù)據(jù)。Webwork前端也可以使用JSTL,但它同時(shí)支持:velocity、freemaker、jspparer、xml。
Struts的FormBean把所有的數(shù)據(jù)都作為String類型,它可以使用工具Commons-Beanutils進(jìn)行類型轉(zhuǎn)化。但它的轉(zhuǎn)化都是在Class級(jí)別,而且轉(zhuǎn)化的類型是不可配置的。類型轉(zhuǎn)化時(shí)的錯(cuò)誤信息返回給用戶也是非常困難的。
WebWork使用OGNL進(jìn)行類型轉(zhuǎn)化,提供了所有基本類型的轉(zhuǎn)化功能。類型轉(zhuǎn)化可以直接對(duì)一個(gè)Class進(jìn)行(Class級(jí)別)轉(zhuǎn)化,也可以對(duì)Class的字段進(jìn)行類型轉(zhuǎn)化。它使用攔截器可以很容易的將類型轉(zhuǎn)化的錯(cuò)誤信息返回給用戶,而且錯(cuò)誤信息可以對(duì)應(yīng)到一個(gè)相應(yīng)的字段。
Webwork2 允許您處理Action可以通過攔截器,就是在每一個(gè)Action處理前或者后進(jìn)行其它操作。它的攔截器可以在配置文件中動(dòng)態(tài)添加,這樣Action和攔截器之間完全解藕,更好的實(shí)現(xiàn)了組件化。
Struts的驗(yàn)證是調(diào)用FormBean的validator()方法,其實(shí)就是對(duì)FormBean的驗(yàn)證。它一般使用框架Commons Validation進(jìn)行數(shù)據(jù)驗(yàn)證處理。它使用了一個(gè)全局的配置文件validation.xml定義了FormBean的驗(yàn)證信息。Struts的FormBean屬性都被認(rèn)為是String類型,所以它在驗(yàn)證時(shí)也需要額外的類型轉(zhuǎn)化。
WebWork使用Xwork的驗(yàn)證框架進(jìn)行驗(yàn)證處理,它可以通過配置攔截器來激活。它可以為每個(gè)需要驗(yàn)證的Class指定一個(gè)xml驗(yàn)證文件,也可以為一個(gè)Class在不同的情況指定不同的xml驗(yàn)證文件。WebWork證可以給每個(gè)Action類指定對(duì)應(yīng)的驗(yàn)證文件,也可以給Action的字段去指定驗(yàn)證文件。通過攔截器來組裝Action和其驗(yàn)證文件,使它們之間完全解藕。
Struts創(chuàng)建一個(gè)Action,如果想控制它的執(zhí)行順序?qū)?huì)非常困難。甚至你要重新去寫Servlet來實(shí)現(xiàn)你的這個(gè)功能需求。
在這個(gè)方面,WebWork的攔截器棧提供了強(qiáng)大的功能。Action的所有切面功能都有攔截器來實(shí)現(xiàn)(比如:取得request請(qǐng)求參數(shù)、驗(yàn)證處理等),這樣你就可以用攔截器棧來組織攔截器的執(zhí)行順序。例如:你需要在使用request請(qǐng)求參數(shù)來設(shè)置Action屬性之前,使用IoC框架設(shè)置Action的屬性,反之已然。這時(shí),你就可以為package或Action指定一個(gè)攔截器棧來實(shí)現(xiàn)。
2、 一本好書:Java Open Source Programming : with XDoclet, JUnit, WebWork, Hibernate,里面有很好的WebWork教程。它附帶的源代碼可以去
3、 Confluence(http://www.atlassian.com/software/confluence)是專業(yè)的J2EE wiki,用于知識(shí)管理和項(xiàng)目組交流。它使用的架構(gòu)是webwork2+Spring+Hibernate。Confluence雖是商業(yè)軟件,不過對(duì)于 OpenSource的項(xiàng)目它全部免費(fèi)提供。它的架構(gòu)思想很值得我們?nèi)W(xué)習(xí)。
4、 OpenReports(http://www.opensourcesoft.net)是一個(gè)開源的項(xiàng)目,基于Web的報(bào)表系統(tǒng)。它用到的技術(shù)有:WebWork 2.0、Velocity和 Hibernate。
location可以是一個(gè)絕對(duì)的URL,如http://www.opensymphony.com/也可以使用相對(duì)的URL。如果location以“/”開頭,則容器認(rèn)為相對(duì)于當(dāng)前Web應(yīng)用的根,否則,容器將解析為相對(duì)于當(dāng)前請(qǐng)求的URL。這種重定向結(jié)果,將導(dǎo)致客戶端瀏覽器的請(qǐng)求URL跳轉(zhuǎn)。從瀏覽器中的地址欄中可以看到新的URL地址。
location可以是一個(gè)絕對(duì)的URL,如response.sendRedirect("http://java.sun.com")也可以使用相對(duì)的URL。
如果location以“/”開頭,則容器認(rèn)為相對(duì)于當(dāng)前Web應(yīng)用的根,否則,容器將解析為相對(duì)于當(dāng)前請(qǐng)求的URL。