webwork2.0配置詳解
首先下載WebWork2 的最新版本(http://www.opensymphony.com/webwork/)。
這里我們所談及的WebWork,實際上是Webwork+XWork的總集,Webwork1.x 版本中,
整個框架采用了緊耦合的設計(類似Struts),而2.0 之后,Webwork被拆分為兩個部分,
即Webwork 2.x +XWork 1.x,設計上的改良帶來了系統靈活性上的極大提升。
本例的部署如下圖所示
index.jsp
<body>
<form action="/login.action">
<p align="center">
登錄<br> </p>
用戶名:<input type="text" name="model.username" /><br>
密 碼 :<input type="password" name="model.password" /><br>
<p align="center"><input type="submit" value="提交" name="B1"/><input type="reset" value="重置" name="B2"/></p>
</form>
</body>
</html>
這里的index.jsp實際上是由純html 組成,非常簡單,其中包含一個表單:
<form action="/login.action">
這表明其提交對象為/login.action . 表單中同時包含兩個文本輸入框,
<input type="text" name="model.username" />
<input type="password" name="model.password" />
可以看到,兩個輸入框的名稱均以“model”開頭,這是因為在這里我們采用了WebWork
中Model-Driven的Action驅動模式
標準HTTP協議中并沒有.action結尾的服務資源。我們需要在web.xml中加以設定:
……
<servlet>
<servlet-name>webwork</servlet-name>
<servlet-class>
com.opensymphony.webwork.dispatcher.ServletDispatcher
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>webwork</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
此后,所有以.action結尾的服務請求將由ServletDispatcher 接管。
ServletDispatcher 接受到Servlet Container 傳遞過來的請求,將進行一下幾個動作:
1. 從請求的服務名(/login.action)中解析出對應的Action名稱(login)
2. 遍歷 HttpServletRequest、HttpSession、ServletContext 中的數據,并將其復制到
Webwork的Map實現中,至此之后,所有數據操作均在此Map結構中進行,從而將內部結構與Servlet API相分離。
至此,Webwork 的工作階段結束,數據將傳遞給XWork 進行下一步處理。從這里也可以看
到Webwork和xwork之間的切分點,Webwork為xwork提供了一個面向Servlet 的協議轉換
器,將Servlet 相關的數據轉構轉換成xwork所需要的通用數據格式,而xwork將完成實際的
服務調度和功能實現。
這樣一來,以xwork為核心,只需替換外圍的協議轉換組件,即可實現不同技術平臺之間的
切換(如將面向Servlet的Webwork替換為面向JMS的協議轉換器實現,即可在保留應用邏
輯實現的情況下,實現不同外部技術平臺之間的移植)。
3. 以上述信息作為參數,調用ActionProxyFactory創建對應的ActionProxy實例。
ActionProxyFactory 將根據Xwork 配置文件(xwork.xml)中的設定,創建
ActionProxy實例,ActionProxy中包含了Action的配置信息(包括Action名稱,
對應實現類等等)。
4. ActionProxy創建對應的Action實例,并根據配置進行一系列的處理程序。包括
執行相應的預處理程序(如通過Interceptor 將Map 中的請求數據轉換為Action
所需要的Java 輸入數據對象等),以及對Action 運行結果進行后處理。
ActionInvocation 是這一過程的調度者。而com.opensymphony.xwork.
DefaultActionInvocation 則是XWork 中對ActionInvocation 接口的標準實現,如
果有精力可以對此類進行仔細研讀,掌握了這里面的玄機,相信XWork的引擎
就不再神秘。
下面我們來看配置文件:
xwork.xml:
<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<action name="login" class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
</result>
<interceptor-ref name="params" />
<interceptor-ref name="model-driven"/>
</action>
</package>
</xwork>
⑴ include
通過include 節點,我們可以將其他配置文件導入到默認配置文件xwork.xml 中。
從而實現良好的配置劃分。
這里我們導入了Webwork 提供的默認配置webwork-default.xml(位于
webwork.jar 的根路徑)。
⑵ package
XWork中,可以通過package對action進行分組。類似Java 中package和class的
關系。為可能出現的同名Action提供了命名空間上的隔離。
同時,package還支持繼承關系。在這里的定義中,我們可以看到:
extends="webwork-default"
"webwork-default"是webwork-default.xml文件中定義的package,這里通
過繼承,"default" package 自動擁有"webwork-default" package 中的所有
定義關系。
這個特性為我們的配置帶來了極大便利。在實際開發過程中,我們可以根據自身
的應用特點,定義相應的package模板,并在各個項目中加以重用,無需再在重復
繁瑣的配置過程中消耗精力和時間。
此外,我們還可以在Package節點中指定namespace,將我們的action分為若干個
邏輯區間。如:
<package name="default" namespace="/user"
extends="webwork-default">
就將此package中的action定義劃歸為/user 區間,之后在頁面調用action的時候,
我們需要用/user/login.action 作為form action 的屬性值。其中的/user/就指定了此
action的namespace,通過這樣的機制,我們可以將系統內的action進行邏輯分類,
從而使得各模塊之間的劃分更加清晰。
⑶ action
Action配置節點,這里可以設定Action的名稱和對應實現類。
⑷ result
通過result 節點,可以定義Action 返回語義,即根據返回值,決定處理模式以及
響應界面。
這里,返回值"success"(Action 調用返回值為String 類型)對應的處理模式為
"dispatcher"。
可選的處理模式還有:
1. dispatcher
本系統頁面間轉向。類似forward。
2. redirect
瀏覽器跳轉。可轉向其他系統頁面。
3. chain
將處理結果轉交給另外一個Action處理,以實現Action的鏈式處理。
4. velocity
將指定的velocity模板作為結果呈現界面。
5. xslt
將指定的XSLT 作為結果呈現界面。
隨后的param節點則設定了相匹配的資源名稱。
⑷ interceptor-ref
設定了施加于此Action的攔截器(interceptor)。關于攔截器,請參見稍后的“XWork
攔截器體系”部分。
interceptor-ref定義的是一個攔截器的應用,具體的攔截器設定,實際上是繼
承于webwork-default package,我們可以在webwork-default.xml 中找到
對應的"params"和"model-driven"攔截器設置:
<interceptors>
……
<interceptor name="params"
class="com.opensymphony.xwork.interceptor.ParametersInt
erceptor" />
<interceptor name="model-driven"
class="com.opensymphony.xwork.interceptor.ModelDrivenIn
terceptor" />
……
</interceptors>
"params"大概是Webwork 中最重要、也最常用的一個Interceptor。上面曾經將
MVC工作流程劃分為幾個步驟,其中的第一步:
“將 Web 頁面中的輸入元素封裝為一個(請求)數據對象”
就是通過"params"攔截器完成。Interceptor 將在Action 之前被調用,因而,
Interceptor 也成為將Webwork傳來的MAP 格式的數據轉換為強類型Java 對象的
最佳實現場所。
"model-driven"則是針對Action 的Model驅動模式的interceptor 實現。具體描
述請參見稍后的“Action驅動模式”部分
很可能我們的Action 都需要對這兩個interceptor 進行引用。我們可以定義一個
interceptor-stack,將其作為一個interceptor 組合在所有Action 中引用。如,上面
的配置文件可修改為:
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<interceptors>
<interceptor-stack name="modelParamsStack">
<interceptor-ref name="params" />
<interceptor-ref name="model-driven" />
</interceptor-stack>
</interceptors>
<action name="login"
class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
<param name="location">/index.jsp</param>
</result>
<interceptor-ref name="modelParamsStack" />
</action>
</package>
</xwork>
通過引入interceptor-stack,我們可以減少interceptor 的重復申明。
下面是我們的Model對象:
LoginInfo.java:
public class LoginInfo {
private String password;
private String username;
private List messages = new ArrayList();
private String errorMessage;
public List getMessages() {
return messages;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
很簡單,這只是一個純粹的值對象(Value-Object)。這里,它扮演著模型(Model)的
角色,并與Action的輸入輸出密切相關。
與 SpringMVC中的Command對象不同,Webwork 中的Model對象,扮演著承上啟下
的角色,它既是Action的輸入參數,又包含了Action處理的結果數據。
換句話說,輸入的Http請求參數,將被存儲在Model對象傳遞給Action進行處理,Action
處理完畢之后,也將結果數據放置到Model 對象中,之后,Model 對象與返回界面融合生
成最后的反饋頁面。
也正由于此,筆者建議在實際開發中采用Model-Driven 模式,而非Property-Driven 模
式(見稍后“Action驅動模式”部分),這將使得業務邏輯更加清晰可讀。
對應的Action代碼
public class LoginAction implements Action, ModelDriven {
private final static String LOGIN_FAIL="loginfail";
LoginInfo loginInfo = new LoginInfo();
public String execute() throws Exception {
if ("erica".equalsIgnoreCase(loginInfo.getUsername())
&& "mypass".equals(loginInfo.getPassword())) {
//將當前登錄的用戶名保存到Session
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
//出于演示目的,通過硬編碼增加通知消息以供顯示
loginInfo.getMessages().add("message1");
loginInfo.getMessages().add("message2");
loginInfo.getMessages().add("message3");
return SUCCESS;
}else{
loginInfo.setErrorMessage("Username/Password Error!");
return LOGIN_FAIL;
}
}
public Object getModel() {
return loginInfo;
}
}
1. Action
Action接口非常簡單,它指定了Action的入口方法(execute),并定義了
幾個默認的返回值常量:
public interface Action extends Serializable {
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public String execute() throws Exception;
}
SUCCESS、NONE、ERROR、INPUT、LOGIN 幾個字符串常量定義了常用的
幾類返回值。我們可以在Action 實現中定義自己的返回類型,如本例中的
LOGIN_FAIL定義。
而execute方法,則是Action的入口方法,XWork將調用每個Action的execute
方法以完成業務邏輯處理。
2. ModelDriven
ModelDriven接口更為簡潔:
public interface ModelDriven {
Object getModel();
}
ModelDriven僅僅定義了一個getModel方法。XWork在調度Action時,將通
過此方法獲取Model 對象實例,并根據請求參數為其設定屬性值。而此后的
頁面返回過程中,XWork 也將調用此方法獲取Model 對象實例并將其與設定
的返回界面相融合。
注意這里與Spring MVC 不同,Spring MVC 會自動為邏輯處理單元創建
Command Class實例,但Webwork不會自動為Action創建Model對象實例,
Model 對象實例的創建需要我們在Action 代碼中完成(如LoginAction 中
LoginInfo對象實例的創建)。
另外,如代碼注釋中所描述,登錄成功之后,我們隨即將username保存在Session之中,
這也是大多數登錄操作必不可少的一個操作過程。
這里面牽涉到了Webwork中的一個重要組成部分:ActionContext。
ActionContext為Action提供了與容器交互的途徑。對于Web 應用而言,與容器的交互
大多集中在Session、Parameter,通過ActionContext我們在代碼中實現與Servlet API無關的
容器交互。
如上面代碼中的:
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
同樣,我們也可以操作Parameter:
ActionContext ctx = ActionContext.getContext();
Map params = ctx.getParameters();
String username = ctx.getParameters("username");
上述的操作,將由XWork根據當前環境,調用容器相關的訪問組件(Web 應用對應的
就是Webwork)完成。上面的ActionContext.getSession(),XWork 實際上將通過Webwork
提供的容器訪問代碼“HttpServletRequest.getSession()”完成。
注意到,ActionContext.getSession返回的是一個Map類型的數據對象,而非HttpSession。
這是由于WebWork對HttpSession進行了轉換,使其轉變為與Servlet API無關的Map對象。
通過這樣的方式,保證了Xwork 所面向的是一個通用的開放結構。從而使得邏輯層與表現
層無關。增加了代碼重用的可能。
此 外, 為了提供與Web 容器直接交互的可能。WebWork 還提供了一個
ServletActionContext實現。它擴展了ActionContext,提供了直接面向Servlet API的容器訪
問機制。
我們可以直接通過ServletActionContext.getRequest 得到當前HttpServletRequest 對象的
獲得如此靈活性的代價就是,我們的代碼從此與ServletAPI 緊密耦合,之后系統在不
同平臺之間移植就將面臨更多的挑戰(同時單元測試也難于進行)。
平臺移植的需求并不是每個應用都具備。大部分系統在設計階段就已經確定其運行平
臺,且無太多變更的可能。不過,如果條件允許,盡量通過ActionContext 與容器交互,而
不是平臺相關的ServletActionContext,這樣在順利實現功能的同時,也獲得了平臺遷移上
的潛在優勢,何樂而不為。
登錄成功界面:
main.jsp:
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<p align="center">Login Success!</p>
<p align="center">Welcome!
<ww:property value="#session['username']"/>
</p>
<p align="center">
<b>Messages:</b><br>
<ww:iterator value="messages" status="index">
<ww:if test="#index.odd == true">
!<ww:property/><br>
</ww:if>
<ww:else>
*<ww:property/><br>
</ww:else>
</ww:iterator>
</p>
</body>
</html>
這里我們引入了Webwork的taglib,如頁面代碼第一行的申明語句。
下面主要使用了三個tag:
<ww:property value="#session['username']"/>
讀取Model對象的屬性填充到當前位置。
value指定了需要讀取的Model對象的屬性名。
這里我們引用了LoginAction在session中保存的’username’對象。
由于對應的Model(LoginInfo)中也保存了username 屬性。下面的語句與之
效果相同:
<ww:property value="username"/>
與 JSP2中的EL類似,對于級聯對象,這里我們也可以通過“.”操作符獲得
其屬性值,如value="user.username"將得到Model 對象中所引用的user
對象的username 屬性(假設LoginInfo中包含一個User 對象,并擁有一個名
為“username”的屬性)。
關于EL的內容比較簡單,本文就不再單獨開辟章節進行探討。
Webwork中包括以下幾種特殊的EL表達式:
parameter[‘username’] 相當于request.getParameter(“username”);
request[‘username’] 相當于request.getAttribute(“username”);
session[‘username’] 從session中取出以“username”為key的值
application[‘username’] 從ServletContext中取出以“username”為key
的值
注意需要用“#”操作符引用這些特殊表達式。
另外對于常量,需要用單引號包圍,如#session['username'] 中的
'username'。
<ww:iterator value="messages" status="index">
迭代器。用于對java.util.Collection、java.util.Iterator、java.util.Enumeration,、
java.util.Map、Array類型的數據集進行循環處理。
其中,value屬性的語義與<ww:property>中一致。
而 status屬性則指定了循環中的索引變量,在循環中,它將自動遞增。
而在下面的<ww:if>中,我們通過“#”操作符引用這個索引變量的值。
索引變量提供了以下幾個常用判定方法:
first 當前是否為首次迭代
last 當前是否為最后一次迭代
odd 當前迭代次數是否奇數
even 當前迭代次數是否偶數
<ww:if test="#index.odd == true">和<ww:else>
用于條件判定。
test屬性指定了判定表達式。表達式中可通過“#”操作符對變量進行引用。
表達式的編寫語法與java 表達式類似。
類似的,還有<ww:elseif test="……">。
登錄失敗界面
實際上,這個界面即登錄界面index.jsp。只是由于之前出于避免干擾的考慮,隱藏了
index.jsp中顯示錯誤信息的部分。
完整的index.jsp如下:
<%@ page pageEncoding="gb2312"
contentType="text/html;charset=gb2312"%>
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<form action="/login.action">
<p align="center">
登錄<br>
<ww:if test="errorMessage != null">
<font color="red">
<ww:property value="errorMessage"/>
</font>
</ww:if>
</p>
用戶名:
<input type="text" name="model.username" />
<br>
密 碼 :
<input type="password" name="model.password" />
<br>
<p align="center">
<input type="submit" value="提交" name="B1"/>
<input type="reset" value="重置" name="B2"/>
</p>
</form>
</body>
</html>
這里首先我們進行判斷,如果Model中的errorMessage不為null,則顯示錯誤信息。這
樣,在用戶第一次登錄時,由于Model對象尚未創建,errorMessage自然為null,錯誤信息
不會顯示,即得到了與之前的index.jsp同樣的效果。