1. 程式人生 > >淺談Struts2

淺談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的成員域contextOgnlContext物件,即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物件的內容,注意看地址。

暫時就這麼多吧。以上