淺談Struts2
學過SSH框架很長一段時間了,一直沒有很系統的總結一下,這裡先簡單談談Struts2。
為什麼要用Struts2?
這裡列舉一些Servlet的缺點:
1、每寫一個servlet在web.xml中都要做相應的配置。如果有多很servlet,會導致web.xml內容過於繁多。
2、這樣的結構不利於分組開發。
3、在servlet中,doGet方法和doPost方法有HttpServletRequest和HttpServletResponse引數。這兩個引數與容器相關,如果想在servlet中作單元測試,則必須初始化這兩個引數。
4、如果一個servlet中有很多個方法,則必須採用傳遞引數的形式,分解到每一個方法中。
而而而而而而而而而而。。。。先了解一下Struts2是什麼。
Struts2是一個遵循MVC的Web層框架。
先看一下基於Web的MVC三層架構:
這是一個MVC三層架構的基本模式,三層架構中的顯示層這裡是B/S結構的Web應用。而MVC就是Model、View、Controller。
說好的Struts2是一個Web層的MVC框架呢?在Struts2中MVC是什麼呢?
Struts2利用過濾器,攔截客戶端的請求。客戶端傳送請求,經過struts2的過濾器,將HttpServletRequest引數和HttpServletResponse引數封裝,利用java反射機制將請求分派給對映的Action。根據Action的執行結果,轉向其他Action或jsp頁面
Struts2 的Action實現了與Servlet API的解耦,使得在Action裡面不需要再直接去引用和使用HttpServletRequest與HttpServletResponse等介面。因而使得Action的單元測試更加簡單,而且強大的型別轉換也使得我們少做了很多重複的工作。
下面看一下Struts2的原理圖:
具體過程大致如下:
1、客戶端向Servlet容器(例如Tomcat)傳送請求
2、這個請求經過一系列的過濾器(Filter)
3、接著FilterDispatcher(現已過時)被呼叫,FilterDispatcher詢問ActionMapper來決定這個請是否需要呼叫某個Action
4、如果ActionMapper決定需要呼叫某個Action,FilterDispatcher把請求的處理交給ActionProxy
5、ActionProxy通過Configuration Manager詢問框架的配置檔案,找到需要呼叫的Action類
6、ActionProxy建立一個ActionInvocation的例項。
7、ActionInvocation例項使用命名模式來呼叫,在呼叫Action的過程前後,涉及到相關攔截器(Intercepter)的呼叫。(此處採用了AOP,一系列的攔截器即通知,Action的方法為切入點)
8、Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果通常是(但不總是,也可 能是另外的一個Action鏈)一個需要被表示的JSP或者FreeMarker的模版。在表示的過程中可以使用Struts2 框架中繼承的標籤。在這個過程中需要涉及到ActionMapper
在上述過程中所有的物件(Action,Results,Interceptors,等)都是通過ObjectFactory來建立的。
FilterDispatcher是早期struts2的過濾器,2.1.3後使用StrutsPrepareAndExecuteFilter。StrutsPrepareAndExecuteFilter,prepare進行配製的匯入;execute表示進行過濾,指doFilter方法,即將request請求,轉發給對應的 action去處理。
上面是Struts2的基本原理,下面看一下Struts2使用主要涉及的幾個方面:攔截器,驗證,型別轉換,屬性驅動、模型驅動,OGNL。
攔截器
Struts2自帶的攔截器有35個之多。例如:輸入驗證是由名為validation攔截器處理的,如果禁用該攔截器,輸入驗證將停止工作;檔案上傳依靠名為fileUpload的攔截器。
Struts2自帶的預設攔截器足以滿足絕大多數的應用程式的需要,但也可以自定義攔截器。
自定義攔截器
1、編寫一個類,實現com.opensymphony.xwork2.interceptor.Interceptor
2、主要實現public String intercept(ActionInvocation invocation) throws Exception{}方法
3、攔截器定義好後,要在配置檔案中進行註冊:
<interceptors>
<interceptor name=" interceptorName" class="className"/>
</interceptors>
4、配置檔案中的動作,通過 <interceptor-ref name=" interceptorName "></interceptor-ref> 使用該攔截器.
注意:一旦動作中使用了自定義的攔截器,那麼預設的就不起作用了。一般應該採用如下的做法:
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name=" interceptorName"></interceptor-ref>
多個動作類都要使用的話,可以通過package來進行組合。
驗證
有時候對於從客戶端傳來的資料需要驗證,例如登入頁面,驗證使用者名稱不能為空,密碼也不能為空,並且長度不能小於6位數。
驗證的方法有分為以下幾種:
1、程式設計方式
動作類中的所有方法進行驗證:
步驟:
a、動作類繼承ActionSupport
b、覆蓋呼叫public void validate()方法
c、在validate方法中,編寫不符合要求的程式碼判斷,並呼叫父類的addFieldError(String fieldName,String errorMessage)
如果fieldError(存放錯誤資訊的Map)有任何的元素,就是驗證不通過,動作方法不會執行。Struts2框架會返回到name=input的result
d、在name=input指定的頁面上使用struts2的標籤顯示錯誤資訊。<s:fielderror/>
動作類中指定的方法進行驗證:
編寫步驟與上面相同,驗證方法書寫有要求:
public void validateXxx() Xxx代表的是要驗證的動作方法名,其中要把動作方法名的首字母變為大寫。
2、基於XML配置檔案的方式
①動作類中的所有方法進行驗證:
在動作類的包中,建立一個名稱為:動作簡單類名-validation.xml ,比如要驗證的動作類名是UserAction UserAction-validation.xml,內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.3//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
<validators>
<field name="username">
<!-- 內建驗證器都是定義好的,在xwork-core.jar com.opensymphony.xwork2.validator.validators包中的default.xml檔案中 -->
<field-validator type="requiredstring"><!-- 不能為null或者""字串,預設會去掉前後的空格 -->
<message>使用者名稱不能為空</message>
</field-validator>
</field>
</validators>
②動作類中指定的方法進行驗證:
配置檔案的名稱書寫有一定要求:動作類名-動作名(配置檔案中的動作名)-validation.xml 例如UserAction-user_add-validation.xml
3、自定義基於XML的驗證器
a、編寫一個類,繼承FieldValidatorSupport類。
b、在public void validate(Object object)編寫你的驗證邏輯,不符合要求的就向fieldErrors中放訊息
c、一定註冊驗證器才能使用
在WEB-INF/classes目錄下建立一個名稱為validators.xml的配置檔案,內容如下:
<validators>
<validator name="strongpassword" class="wz.validators.StrongPasswordValidator"/>
</validators>
d、日後就可以像使用Struts2提供的16個驗證器方式去使用了。
屬性驅動和模型驅動
屬性驅動
條件:
1、頁面中name的屬性和action中的屬性必須保持一致。
2、 Action中的屬性必須有get和set方法。
3、滿足這兩個條件就實現了屬性驅動。
過程:
1、 當執行所有的攔截器的時候,當前請求的action已經放在了物件棧棧頂。
2、 放在物件棧的物件的特點是其屬效能夠直接訪問。
3、 也就是說當執行ParameterInterceptor攔截器的時候,action的所有的屬性在棧頂。
4、 所以只需要給棧頂的action的屬性賦值就可以了。
5、 而ParameterInterceptor攔截器正好完成了此功能。
模型驅動
假設在完成網站的某項功能時,在後臺需要得到20多個屬性。如果用action中的屬性獲取值,就要在action中會寫20個屬性以及其set和get方法。這樣會導致action中的程式碼結構不是很好。
模型驅動很好的解決了這個問題。使用javaBean物件來封裝請求引數,實現ModelDriven介面並定義模型成員域即可。
例如:
public class ModelDriverAction extends ActionSupport implements ModelDriven<User>{
private User model = new User();
public User getModel() {
return this.model;
}
public String execute(){
return "modeldriver";
}
}
當瀏覽器提交對當前Action的請求時,先經過攔截器。其中有一個攔截器為ModelDrivenInterceptor,從這個原始碼可以看出,這個攔截器的作用就是獲取實現了ModelDriver介面的action的模型驅動。在這裡為user。然後把模型驅動利用push方法壓入到物件棧棧頂。這樣就能直接通過屬性進行回顯和賦值了。
到底是用屬性驅動和是模型驅動呢?
(1)最好統一整個系統中的Action使用的驅動模型,即要麼都是用屬性驅動,要麼都是用模型驅動。
(2)如果DB中的持久層的物件與表單中的屬性都是一一對應的話,那麼就使用模型驅動,程式碼要整潔很多。
(3)如果表單的屬性不是一一對應的話,那麼就應該使用屬性驅動,否則,你的系統就必須提供兩個Bean,一個對應表單提交的資料,另一個用與持久層。
型別轉換
從屬性驅動的角度考慮,中如果屬性中要求接受的不是String型別,而是其他型別呢?struts2將做自動的轉化。
客戶端表單的每一項輸入之可能是一個String或一個String陣列。在伺服器端,必須先把這些String值轉換為特定的資料型別,才能進行相應的處理把請求引數對映到動作屬性的工作由Parameters攔截器負責,它是defaultStack攔截器棧的一員。所有的請求引數都是String型別,但並非所有的動作屬性都是String型別,所以每一種非String型別的動作屬性需要對相關的請求引數進行型別轉換。有些Struts2可以自動轉化,而有些需要我們手動編寫轉換的程式碼。
具體方式:
1、編寫一個類,繼承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter
2、覆蓋掉其中的public Object convertValue(Map<String, Object> context, Object value,Class toType)
context:OGNL表示式的上下文
value:實際的值。使用者輸入的都是字串,但他是一個String陣列。
toType:目標型別
3、註冊型別轉換器
3.1區域性型別轉換器:只對當前的Action有效
具體做法:在動作類相同的包中,建立一個名稱是“動作類名-conversion.properties”的配置檔案,檔案中增加以下內容:要驗證的欄位=驗證器的類全名。例如:birthday=wz.convertor.DateConvertor
3.2全域性型別轉換器:對所有的Action都有效
具體做法:在WEB-INF/classes目錄下,建立一個名稱為"xwork-conversion.properties"的配置檔案,檔案中增加以下內容:目標型別全名=驗證器的類全名。例如:java.util.Date=cn.itcast.convertor.DateConvertor
注意:如果轉換失敗,Struts2框架會尋找name=input的結果頁面
OGNL
OGNL表示式是(Object-Graph Navigation Language)是物件圖形化導航語言。OGNL是一個開源的專案,struts2中預設使用OGNL表示式語言來顯示資料。與serlvet中的el表示式的作用是一樣的。
提起OGNL就不得不提ValueStack了。ValueStack是一個介面,在struts2中使用OGNL表示式實際上是使用實現了ValueStack介面的類OgnlValueStack,這個類是OgnlValueStack的基礎。ValueStack貫穿整個action的生命週期。每一個action例項都擁有一個ValueStack物件。其中儲存了當前action物件和其他相關物件。Struts2把ValueStack物件儲存中名為struts.valueStack的request域中。
當struts接受一個請求時,會迅速建立ActionContext,ValueStack,action。然後把action存放進ValueStack,所以action的例項變數可以被OGNL訪問
ActionContext.getContext()從ThreadLocal中得到本執行緒的ActionContext物件
actionContext物件可以獲取context、application、session、valueStack等物件。後三者其實是從context中取出的。
ActionContext的成員域context是OgnlContext物件,即ValueStack中的context物件
context物件中存放request、session、application、parameters、attr等map以及ValueStack等物件
context map與valueStack的關係:
1、context中有一個鍵值對,key=com.opensymphony.xwork2.util.ValueStack.ValueStack,value=valueStack,即valueStack
2、valueStack中成員域包括CompoundRoot root和OgnlCotext context;。沒錯,就是上面的context。
3、而ActionContext中的成員域context,就是上面的context。
下面是ActionContext中context物件的內容,注意看地址。
暫時就這麼多吧。以上