實現Action
Action是struts2應用的核心,開發中需要大量的Action類,並在struts.xml中配置Action。Action中包含了對用戶請求的處理邏輯,Action類也被稱爲業務控制邏輯器。
struts2採用低侵入式設計,它不要求Action類繼承任何的struts的基類或者實現任何struts接口。struts2的Action類是普通的POJO類(通常應該帶一個無參的方法),從而有很好的代碼複用性。
struts2通常直接使用Action來封裝HTTP參數,因此,Action類還應該包含於請求參數對應的實例變量,並且爲這些實例變量提供相應的setter和getter方法。
比如用戶請求包含productName和productDesc兩個請求參數,Action類應該提供productName和productDesc兩個實例變量來封裝用戶請求參數,並且爲productName和productDesc提供對應的setter和getter方法。下面是處理該請求的Action類的代碼片段:
//處理用戶請求的Action類,只是一個POJO,無需繼承任何基類,或實現任何接口
public class Product {
//提供實例變量來封裝HTTP請求參數
private Integer productId;
private String productName;
private String productDesc;
private double productPrice;
//相應的setter和getter方法
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductDesc() {
return productDesc;
}
public void setProductDesc(String productDesc) {
this.productDesc = productDesc;
}
public double getProductPrice() {
return productPrice;
}
public void setProductPrice(double productPrice) {
this.productPrice = productPrice;
}
//Action類默認處理用戶請求的方法:execute方法
public String execute(){
...
//返回處理結果字符串
return resultStr;
}
雖然Action需要處理的請求包含productName和productDesc等HTTP請求參數,Action類也可以不包含它們對應的實例變量。因爲系統是通過對應的setter和getter方法來處理請求參數的,而不是通過實例變量來處理請求參數的。即Action類裏包含對應實例變量與否不重要,重要的是需要包含void setProductName(String productName)和String getProductName()方法。
Action類裏的實例變量不僅可以用來封裝請求參數,還可以用來封裝處理結果。如果希望將服務器提示的“註冊成功”或其他信息在下一個頁面輸出,就可以在Action類中增加一個tip實例變量,併爲之提供對應setter和getter方法,
//封裝服務提示的tip實例變量
private String tip;
//tip對應的setter和getter方法
public String getTip(){
return tip;
}
pubic void setTip(String tip){
this.tip = tip;
}
//Action包含的註冊控制邏輯
public String register() throws Exception{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("歡迎你!" + getUsername() + "你已經註冊成功!");
return SUCCESS;
}
可以在下一個頁面中使用struts2標籤來輸出該實例變量的值。加載JSP頁面中輸出tip實例變量值得代碼片段如下:
<s:property value="tip"/>
系統不嚴格區分Action裏哪個實例變量用於封裝請求參數,哪個實例變量用於封裝處理結果。如果用戶的HTTP請求包含名爲tip的請求參數,則系統會調用void setTip(String tip)方法,通過這種方式,名爲tip的請求參數就可以傳給Action實例;如果Action類裏沒有包含對應的方法,則名爲tip的請求參數將無法傳入該Action。
在JSP頁面中輸出Action的實例變量值時,也不區分該實例變量是用於封裝請你參數還是處理結果。因此,struts2標籤既可以輸出Action的處理結果也可以輸出HTTP請求參數值。
Action接口和Action基類
爲規範Action類開發,struts2提供了一個Action接口,給接口定義了struts2的Action處理類應該實現的規範:
public interface Action {
//定義Action接口裏包含的一些結果字符串
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";
//定義處理用戶請求的execute方法
public String execute() throws Exception;
}
Action接口裏定義了一個execute方法,該接口的規範定義了Action類應該包含一個execute方法,該方法返回一個字符串。該接口還定義了5個字符串常量,用於統一execute方法的返回值。一般Action類處理用戶請求後,有人喜歡返回welcome字符串,有人喜歡返回success字符竄… …這樣不利於統一關聯,故Action接口定義了這5個字符串常量:ERROR、
INPUT、LOGIN和SUCCESS,分別代表特定含義。但是,如果開發者希望用自己的字符串作爲邏輯視圖名,還是可以被允許的,但這並不利用後期維護。
struts2還爲Action接口提供一個實現類ActionSupport,其包含相關方法等見圖
AcitonSupport是個默認的Action實現類,該類裏有很多默認方法,包括獲取國際化信息的方法、數據校驗的方法、默認的處理用戶請求的方法等。它是struts2默認的Action處理類,如果讓開發者的Action處理類繼承ActionSupport類,會大大簡化Action的開發。
Action訪問Servlet API
struts2的Action沒有與任何Servlet API耦合,從而能輕鬆地測試該Action。但是Web應用的控制器經常都是要求訪問Servlet API的,例如跟蹤HTTPSession狀態等,通常Web應用要訪問的Servlet API有HttpServletRequest、HttpSession、ServletContext等,這三個接口分別代表JSP內置對象中的request、session和application。
與Servlet API解耦的訪問方式 :
爲了避免與 Servlet API 耦合在一起, 方便 Action 做單元測試, Struts2 對 HttpServletRequest, HttpSession 和 ServletContext 進行了封裝, 構造了 3 個 Map 對象來替代這 3 個對象, 在 Action 中可以直接使用 HttpServletRequest, HttpServletSession, ServletContext 對應的 Map 對象來保存和讀取數據.
struts2提供了ActionContext類,ActionContext 是 Action 執行的上下文對象, 在 ActionContext 中保存了 Action 執行所需要的所有對象, 包括 parameters, request, session, application 等.
獲取 HttpSession 對應的 Map 對象:
public Map getSession()
獲取 ServletContext 對應的 Map 對象:
public Map getApplication()
獲取請求參數對應的 Map 對象:
public Map getParameters()
獲取 HttpServletRequest 對應的 Map 對象:
public Object get(Object key): ActionContext 類中沒有提供類似 getRequest() 這樣的方法來獲取 HttpServletRequest 對應的 Map 對象. 要得到 HttpServletRequest 對應的 Map 對象, 可以通過爲 get() 方法傳遞 “request” 參數實現。
完整方法如下:
與 Servlet 耦合的訪問方式:
用ActionContext訪問Servlet API不是直接獲得Servlet API的實例。爲在Action中直接訪問Servlet API,struts2提供如下接口:
|>ServletContextAware:實現該接口的Action可以直接訪問Web應用的ServletContext實例。
|>ServletRequestAware:實現該接口的Action可以直接訪問用戶請求的HttpServletRquest實例。
|>ServletResponseAwre:實現該接口的Action可以直接訪問服務器響應的HttpServletResponse實例。
另外,struts2還提供一個ServletActionContext工具類直接訪問Servlet API,
藉助ServletActionContext類可以在Action中訪問Serlevt API,並可以避免Action類需要實現XxxAware接口,但該Action依然與Servlet API直接耦合,測試時需要有 Servlet 容器, 不便於對 Action 的單元測試,一樣不利於高層次的解耦。
Action的動態方法調用
這篇寫Action相關,本來應該還涉及到Action的基本配置,但前面一篇講開發流程時基本介紹到,這裏不再贅言,直接看Action的動態方法調用。
很多時候要求一個Action內包含多個控制處理邏輯。比如對同一個表單,用戶可能要通過登錄或註冊兩個不同提交按鈕來提交同一個表單系統需要使用Action不同方法來處理用戶請求,這就需要讓同一個Action裏包含多個控制處理邏輯。
”登錄”按鈕使用登錄邏輯處理請求,“註冊”按鈕使用註冊邏輯處理請求。可採用DMI(Dynamic Method Invocaton,動態方法調用)來處理請求。動態方法調用中表單元素的action並不是直接等於某個Action的名字,而是按如下形式來指定表單的action屬性。
fun(){
<!--action屬性爲actionName!methodName的形式-->
target.action="action!methodName";
}
如我們可以將JSP頁面“註冊”按鈕的代碼寫成:
<!--註冊按鈕沒有任何動作,但單擊該按鈕時觸發regist函數-->
<input type"submit" value="註冊" onClick="regist();">
regist函數JavaScript代碼:
function regist(){
//獲取頁面的第一個表單
targetForm = document.forms[0];
targetForm.action = "login!regist";
}
最後一行代碼就是說將該表單提交給login Action的regist方法處理。LoginRegistAction類的代碼如下:
public class LoginRegistAction extends ActionSupport{
//封裝用戶請求參數的兩個成員變量
private String username;
private String password;
//封裝處理結果的tip成員變量
private String tip;
//省略setter和getter方法
...
//Action包含的註冊控制邏輯
public String register() throws Exception{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("歡迎你!" + getUsername() + "你已經註冊成功!");
return SUCCESS;
}
//Action默認包含的控制邏輯
public String execute() throws Exception{
if(getUsername().equals("Jujiu")&&getPassword().equals("1357afy"))
{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("歡迎你!" + getUsername()+"你已經註冊成功!");
return SUCCESS;
}
}
}
默認調用execute方法,當用戶單擊登錄,系統將表單提交給LoginActionAction默認方法,當用戶單擊“註冊”按鈕,該表單的action會被修改爲:login!regist,系統將提交給login Action(即LoginRegistAction)的regist方法處理。這種方式可以使一個Action包含多個邏輯處理,並通過爲表單元素指定不同action屬性來提交給action的不同方法。
使用動態方法調用,其中的方法如regist方法的聲明和系統默認的execute方法除了方法名不一樣外,其他如形參列表、返回值類型都應完全相同。
使用動態方法調用前需要設置struts2運行動態方法調用,即設置struts.enable.DynamicMethodInvocation常量的值爲true。DMI方式存在安全缺陷。不建議常用。
method屬性指定方法和通配符使用
可以使用中的method屬性將一個Action類配置成多個邏輯Action,讓Action調用指定方法。
<struts>
<package name="helloWorld" extends="struts-default">
<action name="login" class="com.afy.LoginRegist">
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
<action name="regist" class="com.afy.LoginRegist" method="regist">
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
</package>
</struts>
這裏定義login和regist兩個邏輯Action,處理類都是LoginRegist,處理邏輯由method方法指定,login的Action對應的處理邏輯是默認的execute方法,而regist的Action對應的處理邏輯是指定的regist方法。相應的JavaScript代碼改爲:
function regist(){
//獲取頁面的第一個表單
targetForm = document.forms[0];
targetForm.action = "regist";
}
我們發現多個< action…/>中的定義大部分相同,造成冗餘,此時可以考慮使用通配符方式。在配置< action…/>,指定name屬性時使用模式字符串(用“*”代表一個或多個任意字符),可以在class、method屬性及< result…/>子元素中使用表達式{N}的形式代替前面第N個星號所匹配的子串。
<struts>
<package name="helloWorld" extends="struts-default">
<action name="*Action" class="com.afy.LoginRegist" method=“{1}”>
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
</package>
</struts>