WebWork教程二

Action的單元測試

  理解了ServletDispatcher,我們就明白了整個框架調用執行的順序。Action雖然是與Web無關,可是它的創建、參數設置、執行與我們的WebWork、XWork緊密關聯在一起,有我們的控制器ServletDispatcher去統一調度,那我們如何去對Action進行獨立的單元測試呢?

  請看下面的例子:使用單元測試框架JUnit對register.User. RegisterAction做單元測試

  見example.register. RegisterActionTest類testExecuteWithProxyFactory()方法:

代碼
public void testExecuteWithProxyFactory() throws Exception{

Map params = new HashMap();
params.put("user.username","Moxie");
params.put("user.password","mypassword");
params.put("user.email","[email protected]");
params.put("user.age",new Integer(23));
Map extraContext = new HashMap();
extraContext.put(ActionContext.PARAMETERS,params);

ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy("example", "register", extraContext);
proxy.setExecuteResult(false);
assertEquals(proxy.execute(),"success");

RegisterAction action = (RegisterAction) proxy.getAction();
assertEquals(action.getUser().getUsername(),"Moxie");
assertEquals(action.getUser().getAge(),23);
}



  下面解說這個方法:

  1、 對象params表示請求參數的Map,在它裏面設置了註冊用戶的信息。extraContext當然就是我們ActionContext上下文的容器,它裏面保存了放置請求參數的對象params

  2、 創建我們的ActionProxy,它傳入的參數有:“example”-這個Action的命名空間,“register”-Action對應的名字,extraContext-存放Actin上下文裏的對象,,執行並將它返回的值與“success”比較,測試Action是否能正確執行完成。注意:proxy.setExecuteResult(false);,因爲我們是單元測試,所以Action執行完成就可以了,不用再去調用結果響應的操作,故將是否執行結果設置爲“false”。

  3、 Action正確執行完成之後,我們也可以測試現在Action的字段裏的數據是否按照我們預期的要求正確設置。從ActionProxy對象裏取得執行的Action,即RegisterAction對象,再取得它的User模型,將其數據與前面設置參數的數據進行比較,判斷它是否等於我們預期設置的數值。

Result Type

  前面我們學習了ServletDispatcher,它是WebWork框架機制的核心。它和Action在我們MVC模式中,扮演着控制器的角色,MVC模式通過控制器實現了我們模型和視圖的分離。WebWork提供了多種活靈活視圖展現方式。

  我們先看看前面用戶註冊例子的展現方式:我們使用的是Jsp和WebWork自帶的標籤庫,Action對應的視圖當然是在xwork.xml配置文件裏設置:

代碼
<action name="register" class="example.register.RegisterAction" >
<result name="success" type="dispatcher">
     <param name="location">register-result.jsp</param>
</result>
<interceptor-ref name="params"/>
</action>


  Result是Action執行完返回的一個字符串常量,它表示Action執行完成的狀態,比如:執行成功、執行失敗等。在我們前面Action的介紹中,詳細介紹了它默認的標準Result,當然Result我們也可以自己定義,只要是一個字符串常量就可以了。

  Result的值在xwork.xml配置文件裏就是result標籤裏“name”的值,name="success"表示Action執行成功,返回“success”就對應此標籤的配置,進行視圖輸出。

  “type”就是我們的Result Type,Result Type是一個類,它在Action執行完成並返回Result之後,決定採用哪一種視圖技術,將執行結果展現給用戶。我們輸出的類型是:type="dispatcher",它對應com.opensymphony.webwork.dispatcher.ServletDispatcherResult這個類,它將執行結果通過javax.servlet.RequestDispatcher的forward()或include()方法調度到Jsp頁面展現。

  我們可以自己開發Result Type,實現我們需要的視圖展現方式。Result Type必需要實現com.opensymphony.xwork..Result接口。在WebWork中,它已經爲我們提供了很多Result Type,實現了視圖部分對JSP, Velocity, FreeMarker, JasperReports,XML等的支持,具體如下表格:


  Result Type
Nname
Class
Dispatcher
dispatcher
com.opensymphony.webwork.dispatcher.ServletDispatcherResult
Redirect
redirect
com.opensymphony.webwork.dispatcher.ServletRedirectResult
Action Chaining
chain
com.opensymphony.xwork.ActionChainResult
Velocity
velocity
com.opensymphony.webwork.dispatcher.VelocityResult
FreeMarker
freemarker
com.opensymphony.webwork.views.freemarker.FreemarkerResult
JasperReports
jasper
com.opensymphony.webwork.views.jasperreports.JasperReportsResult
XML/XSL
xslt
com.opensymphony.webwork.views.xslt.XSLTResult
HttpHeader

com.opensymphony.webwork.dispatcher.HttpHeaderResult


  Dispatcher:通過javax.servlet.RequestDispatcher的forward()或include()方法調度到頁面展現,這樣的頁面一般是Jsp頁面。

  參數(Parameters)
是否必需
描 述
location

執行完成之後轉向的位置
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
例子:

代碼
<result name="success" type="dispatcher">
<param name="location">register-result.jsp</param>
</result>


  也可以簡單寫成這樣:

代碼
<result name="success" type="dispatcher">register-result.jsp</result>


  Redirect:將響應重定向到瀏覽器指定的位置,它將會導致Action執行完成的數據丟失或不再可用。它在程序裏是通過調用javax.servlet.http.HttpServletResponse.sendRedirect(String location)方法,將響應定向到參數location指定的、新的url中。

  參數(Parameters)
是否必需
描 述
location

執行完成之後轉向的位置
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
例子

代碼
<result name="success" type="redirect">
<param name="location">foo.jsp</param>
<param name="parse">false</param>
</result>


  Action Chaining:一種特殊的視圖結果,將Action執行完之後鏈接到另一個Action中繼續執行。新的Action使用上一個Action的上下文(ActionContext)。

  參數(Parameters)
是否必需
描 述
actionName

將要被鏈接的Action名字
namespace

被鏈接的Action的命名空間(namespace),如果不設置,默認的即是當前的命名空間
例子:

代碼
<result name="success" type="chain">
<param name="actionName">bar</param>
<param name="namespace">/foo</param>
</result>


  將要調用的Action如下:

代碼
<action name="bar" class="myPackage.barAction">
      ...
</action>


  Velocity:它類似Jsp的執行環境(使用JavaServlet容器),將Velocity模板轉化成數據流的形式,直接通過JavaServlet輸出。

  參數(Parameters)
是否必需
描 述
location

執行完成之後轉向的位置(一般是.vm頁面)
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
例子:

代碼
<result name="success" type="velocity">
<param name="location">foo.vm</param>
</result>


  FreeMarker:FreeMarker是一個純Java模板引擎;一個普通的基於模板生成文本的工具,它只能應用在Web應用環境中。

  參數(Parameters)
是否必需
描 述
location

執行完成之後轉向的位置
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
contentType

如果不指定,默認的是"text/html"
例子:

代碼
<result name="success" type="freemarker">foo.ftl</result>


  JasperReports:將Action執行的結果通過JasperReports報表形式輸出,可以指定JasperReports支持的輸出格式(PDF、HTML、XLS、CSV、XML等),默認是通過PDF格式輸出。

  參數(Parameters)
是否必需
描 述
location

執行完成之後轉向的位置
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
dataSource

它是Action的一個字段(通常是一個List),OGNL表達式被用來去value stack(OgnlValueStack)重新找回這個dataSource
format

報表生成的數據格式,默認的是pdf
例子:

代碼
<result name="success" type="jasper">
<param name="location">foo.jasper</param>
<param name="dataSource">mySource</param>
<param name="format">CSV</param>
</result>


  或者默認的pdf格式

代碼
<result name="success" type="jasper">
<param name="location">foo.jasper</param>
<param name="dataSource">mySource</param>
</result>


  XML/XSL:將結果轉換爲xml輸出

  參數(Parameters)
是否必需
描述
location

執行完成之後轉向的位置
parse

默認的是“true”,如果設置爲“false”,location參數將不會被OGNL表達式語言解析
例子:

代碼

<result name="success" type="xslt">foo.xslt</result>

 


表達式與言EL和OGNL

OGNL介紹

  OGNL是Object-Graph Navigation Language的縮寫,它是一種功能強大的表達式語言(Expression Language,簡稱爲EL),通過它簡單一致的表達式語法,可以存取對象的任意屬性,調用對象的方法,遍歷整個對象的結構圖,實現字段類型轉化等功能。它使用相同的表達式去存取對象的屬性。

  XWork遵循“不要重複地發明同一個輪子”的理論,它的表達式語言核心用的就是這個OGNL。我們先來看看一個簡單的例子:

  還記得我們用戶註冊的那個例子嗎?我們輸入框的name用到的名字就是OGNL的表達式,比如:用戶名的輸入框:“<input type="text" name="user.username">”,在用戶註冊成功之後我們要顯示用戶註冊的信息,用了“<ww:property value="user.username"/>”。Input輸入框裏的“user.username”,它解析成Java語句爲:getUser().setUsername();,property標籤裏的“user.username”解析爲Java語句:getUser.getUsername();。

  我們的兩個表達式都是相同的,但前一個保存對象屬性的值,後一個是取得對象屬性的值。表達式語言簡單、易懂卻又功能強大,關於OGNL更多的介紹可以去http://www.ognl.org,那裏有很詳細的文檔。

值堆棧-OgnlValueStack

  OGNL在框架中的應用,最主要是支持我們的值堆棧(Value Stack)——OgnlValueStack,它主要的功能是通過表達式語言來存取對象的屬性。用戶界面輸入數據,它會根據保存表達式將數據依次保存到它堆棧的對象中,業務操作完成,結果數據會通過表達式被獲取、輸出。

  還記得我們用戶註冊的例子嗎?下面我們用一段程序來演示它向OgnlValueStack中保存、取得數據的步驟:

代碼
// DemoRegisterValueStack
package example.register;

import com.opensymphony.xwork.util.OgnlValueStack;

/**
* @author moxie-qac
*           [email protected]
*
*/
public class DemoRegisterValueStack {
public void demo(){
RegisterAction action = new RegisterAction();
OgnlValueStack valueStack= new OgnlValueStack();
valueStack.push(action);

valueStack.setValue("user.username","Moxie");
System.out.println("username = "+valueStack.findValue("user.username"));
}

public static void main(String[] args) {
DemoRegisterValueStack demoValueStack = new DemoRegisterValueStack();
demoValueStack.demo();
}
}


  我們來看一看它的demo()方法:

  1、 創建我們的Action(RegisterAction)類的對象action,將action對象壓入堆棧valueStack中。在WebWrok中Action的創建、入棧是在DefaultActionInvocation構造函數中進行的,詳細介紹見:ServletDispatcher原理。

  2、 通過表達式語言,調用堆棧對象的get()、set()方法,設置該對象的值。

public void setValue(String expr, Object value)

語句:valueStack.setValue("user.username","Moxie");

的作用等同於:action.getUser().setUsername("Moxie");

  3、 通過表達式語言,去堆棧對象中查找我們前面保存的值,並在控制檯打印。valueStack.findValue("user.username")等同與語句:

action.getUser().getUsername()

最後控制檯打印的結果:

username = Moxie


CompoundRoot

在OgnlValueStack中,一個堆棧其實是一個List。查看OgnlValueStack你會發現,堆棧就是com.opensymphony.xwork.util.CompoundRoot類的對象:

代碼
public class CompoundRoot extends ArrayList {
//~ Constructors /////////////////////////////////////
public CompoundRoot() {
}
public CompoundRoot(List list) {
super(list);
}
//~ Methods ////////////////////////////////////////////
public CompoundRoot cutStack(int index) {
return new CompoundRoot(subList(index, size()));
}
public Object peek() {
return get(0);
}
public Object pop() {
return remove(0);
}
public void push(Object o) {
add(0, o);
}
}


我們通過表達式向堆棧對象操作時,我們並不知道堆棧中有哪些對象。OgnlValueStack會根據堆棧由上向下的順序(先入棧在下面,最後入棧在最上面)依次去查找與表達式匹配的對象方法,找到即進行相應的存取操作。假設後面對象也有相同的方法,將不會被調用。

下面我們看一個對OgnlValueStack操作的程序,它主要演示瞭如何對Map對象的存取和OgnlValueStack堆棧的原理:

代碼
/*
* Created on 2004-6-15
* DemoGroupValueStack.java
*/
package example.register;

import com.opensymphony.xwork.util.OgnlValueStack;

/**
* @author moxie-qac
*           [email protected]
*
*/
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("[email protected]");

User qian = new User();
qian.setUsername("qian");
qian.setEmail("[email protected]");

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
*           [email protected]
*
*/
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(){
//執行業務操作
return null;
}
public String toString(){
return users.toString();
}
}


注意:

1、Map屬性的存取,它的表達式語言如:users['zhao'],注意它用’’來引用HashMap的key字符串。

2、demoModels()方法演示了OgnlValueStack中堆棧的原理,請特別注意它的[0].toString、[1].toString、[2].toString,它們依次調用堆棧中對象的toString()方法,並逐一的減少堆棧最上面的對象。

控制檯輸出的結果如下:

users['zhao'] = username=zhao;password=null;[email protected];age=0
users['qian'] = username=qian;password=null;[email protected];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(攔截器)框架

  Interceptor(攔截器)將Action共用的行爲獨立出來,在Action執行前後運行。這也就是我們所說的AOP(Aspect Oriented Programming,面向切面編程),它是分散關注的編程方法,它將通用需求功能從不相關類之中分離出來;同時,能夠使得很多類共享一個行爲,一旦行爲發生變化,不必修改很多類,只要修改這個行爲就可以。

  Interceptor將很多功能從我們的Action中獨立出來,大量減少了我們Action的代碼,獨立出來的行爲具有很好的重用性。XWork、WebWork的許多功能都是有Interceptor實現,可以在配置文件中組裝Action用到的Interceptor,它會按照你指定的順序,在Action執行前後運行。Interceptor在框架中的應用如下圖所示:


  當你提交對Aciton(默認是.action結尾的Url)的請求時,ServletDispatcher會根據你的請求,去調度並執行相應的Action。在Action執行之前,調用被 Interceptor截取,Interceptor在Action執行前後運行。

  我們在用戶註冊的例子中就使用了取得Request請求參數的攔截器,配置文件中<interceptor-ref name="params"/>將攔截器params組裝到RegisterAction中。“params”在我們的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>


  這些都時有框架提供的默認的Interceptor,下面我來看看Interceptor使用的步驟:

  1、 創建一個自己需要的Interceptor類,它必需實現com.opensymphony.xwork.interceptor.Interceptor接口,具體的開發見下面的Interceptor的原理。

  2、 在配置文件(xwork..xml)中申明這個Interceptor類,它放在標籤<interceptor />中,同是<interceptor />標籤嵌入在<interceptors />標籤內部。

  3、 創建Interceptor棧,使用標籤:<interceptor-stack />,讓一組Interceptor可以按次序調用。(可選)

  4、 指定Action所要用到的Interceptor(前面申明過的),可以用<interceptor-ref />或<default-interceptor-ref />標籤。前面的標籤指定某個Action所用到的Interceptor,如果Action沒有被用<interceptor-ref />指定Interceptor,它將使用<default-interceptor-ref />指定的Interceptor。

  框架中給我們提供了很多實用的Interceptor,它的定義上面已經給出,它的具體功能如下:

  * timer:記錄Action執行的時間,並做爲日誌信息輸出;

  * logger:在日誌信息中輸出要執行的Action信息;

  * chain:將前一個執行結束的Action屬性設置到當前的Action中。它被用在ResultType爲“chain”指定結果的Action中,該結果Action對象會從OgnlValueStack中獲得前一個Action對應的屬性,它實現Action鏈之間的數據傳遞;

  * static-params:將xwork.xml配置文件裏定義的Action參數,設置到對應的Action中。Action參數使用<param />標籤,是<action />標籤的直接子元素。我們這裏定義的Action類必需實現com.opensymphony.xwork.config.entities. Parameterizable接口;

  * params:將Request請求的參數設置到相應Action對象的屬性中,用戶註冊例子用到過這個攔截器;

  * model-driven:如果Action實現ModelDriven接口,它將getModel()取得的模型對象存入OgnlValueStack中;

  * component:激活組件功能支持,讓註冊過的組件在當前Action中可用,即爲Action提供IoC(依賴倒轉控制)框架的支持;

  * token:覈對當前Action請求(request)的有效標識,防止重複提交Action請求(request)。

  * token-session:功能同上,但是當提交無效的Action請求標識時,它會將請求數據保存到session中。

  * validation:實現使用xml配置文件({Action}-validation.xml)對Action屬性值進行驗證,詳細請看後面介紹的驗證框架。

  * workflow:調用Action類的驗證功能,假設Action使用ValidationAware實現驗證(ActionSupport提供此功能),如果驗證沒有通過,workflow會將請求返回到input視圖(Action的<result />中定義的)。

  * servlet-config:提供Action直接對HttpServletRequest或HttpServletResponse等JavaServlet api的訪問,Action要實現相應的接口,例如:ServletRequestAware或ServletResponseAware等。如果必需要提供對JavaServlet api的訪問,我們建議使用ServletActionContext,在前面ActionContext章節中有介紹。

  * prepare:在Action執行之前調用Action的prepare()方法,這個方法是用來準備Action執行之前要做的工作。它要求我們的Action必需實現com.opensymphony.xwork. Preparable接口

  * conversionError:用來處理框架進行類型轉化(Type Conversion)時的出錯信息。它將存儲在ActionContext中的類型轉化(Type Conversion)錯誤信息轉化成相應的Action字段的錯誤信息,保存在堆棧中。根據需要,可以將這些錯誤信息在視圖中顯示出來。


Interceptor的原理

  下面我們來看看Interceptor是如何實現在Action執行前後調用的:

  Action和Interceptor在框架中的執行,是由ActionInvocation對象調用的。它是用方法:String invoke() throws Exception;來實現的,它首先會依次調用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());
}
}


  它會在攔截器棧中遍歷Interceptor,調用Interceptor的方法:

String intercept(ActionInvocation invocation) throws Exception;。

  我們一直都提到,Interceptor是在Action前後執行,可是從上面的代碼我們看到的卻是執行完所有Interceptor的intercept()方法之後再去調用我們的Action。“在Action前後執行”是如何實現的呢?我們來看看抽象類AroundInterceptor的intercept()實現:

代碼
public String intercept(ActionInvocation invocation) throws Exception {
String result = null;

before(invocation);
result = invocation.invoke();
after(invocation, result);

return result;
}


  原來在intercept()方法又對ActionInvocation的invoke()方法進行遞歸調用,ActionInvocation循環嵌套在intercept()中,一直到語句result = invocation.invoke();執行結束,即:Action執行完並返回結果result,這時Interceptor對象會按照剛開始執行的逆向順序依次執行結束。這樣before()方法將在Action執行前調用,after()方法在Action執行之後運行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章