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