Struts控制器組件

 

Struts控制器組件負責接受用戶請求、更新模型,以及選擇合適的視圖組件返回給用戶。控制器組件有助于將模型層和視圖層分離,有了這種分離,就可以在同一個模型的基礎上得心應手地開發多種類型的視圖。Struts控制器組建主要包括:

 

·ActionServlet組件:充當Struts框架的中央控制器。

·RequestProcessor組件:充當每個子應用模塊的請求處理器。

·Action組件:負責處理一項具體的業務。

 

Struts框架采用控制器組件來預處理所有的客戶請求,這種集中控制方式可以滿足MVC設計模式的兩大需求:

 

·首先,控制器在用戶輸入數據和模型之間充當媒介 / 翻譯者的角色,提供一些通用功能,如安全、登入和其他針對具體用戶請求的重要服務,當系統的這些通用功能出現需求變更時,部需要修改整個應用,只需要修改局部的控制器組件即可。

·其次,由于所有的請求都經過控制器過濾, 因此可以降低視圖組件之間,以及視圖組件和模型組件之間的相互依賴關系,提高每個組件的相對獨立性。由控制器組件來決定把合適的視圖組件返回給用用戶,這 可以減少視圖組件之間直接的,錯綜復雜的連接關系,使應用更加靈活,便于維護。

 

Struts框架采用ActionServletRequestProcessor組件進行集中控制,并采用Action組件來處理單項業務。

 

 

控制器組件的控制機制

 

Struts的控制器組件主要完成以下任務:

·接受用戶請求

·根據用戶請求,調用合適的模型組件來執行相應的業務邏輯。

·獲取業務邏輯執行結果。

·根據當前狀態以及業務邏輯執行結果,選擇合適的視圖組件返回給用戶。

 

1 Action

 

  org.apache.struts.action.ActionServlet類是Struts框架的核心控制器組件,所有的用戶請求都先有ActionServlet來處理,然后再由ActionServlet把請求轉發給其他組件。Struts框架只允許在一個應用中配置一個ActionServlet類,在應用的生命周期中,僅創建ActionServlet類的一個實例,這個ActionServlet實例可以同時響應多個用戶請求。

 

(a)    Struts框架初始化過程

(1)    調用initInternal()方法,初始化Struts框架內在的消息資源,如與系統日志相關的同志、警告和錯誤消息。

(2)    調用initOther()方法,從web.xml文件中加載ActionServlet的初始化參數,如config參數。

(3)    調用initServlet()方法,從web.xml文件中加載ActionServletURL映射信息。此外還會注冊web.xmlStruts配置文件所使用的DTD文件,這些DTD文件用來驗證web.xmlStruts配置文件的語法。

(4)    調用initModuleConfig()方法,加載并解析子應用模塊的Struts配置文件;創建ModuleConfig對象,把它存儲在ServletContext中。

(5)    調用initModuleMessageResources()方法,加載并初始化默認子應用模塊的消息資源:創建MessageResources對象,把它存儲在ServletContext中。

(6)    調用initModuleDataSources()方法,加載并初始化默認子應用模塊的數據源。如果在Struts配置文件中沒有定義<data-sources>元素,就忽略這一流程。

(7)    調用InitModulePlugins()方法,加載并初始化默認子應用模塊的所有插件。

(8)    當默認子應用模塊被成功地初始化后,如果還包括其他子應用模塊,將重復流程(4)(7),分別對其他子應用模塊進行初始化。

 

(b)    ActionServletprocess()方法

ActionServlet實例接受到HTTP請求之后,在doGet()doPost()方法都會調用process()方法來處理請求。一下是ActionServletprocess()方法的源代碼:

 

protected void process (HttpServletRequest request, HttpServletResponse response)

throw IOException, ServletException {

 

ModuleUtils.getInstance().selectModule(request, getServletContext());

getRequestProcessor(getModuleConfig(request)).process(request, response);

}

 

process()方法中,首先調用org.apache.struts.util.ModuleUtils類的selectModule()方法,這個方法選擇負責處理當前請求的子應用模塊,然后把與子應用模塊相關的ModuleConfigMessageResources對象存儲倒request范圍中,這使得框架的其余組件可以方便地從request范圍中讀取這些對象,從而獲取應用配置信息和消息資源。

process()方法的第二步操作為獲得RequestProcessor類的實例,然后調用RequestProcessor類的process()方法,來完成十幾的預處理請求操作。

 

 

(c)    擴展ActionServlet

Sturts 1.1 之前的版本中,ActionServlet類本身包含了很多處理請求的代碼。從Struts 1.1 開始,多數功能被移到 org.apache.struts.action.RequestProcessor 類中,以便減輕ActionServlet類的控制負擔。

 

盡管新版本的Struts框架允許在應用中創建礦展ActionServlet類的子類,但是這在多數情況下沒有必要,因為控制器的多數控制功能位于RequestProcessor類中。

 

如果實際應用確實需要創建自己的ActionServlet類,則可以創建一個ActionServlet的子類,然后在web.xml文件中配置這個客戶化ActionServlet類。

 

如果覆蓋了init()方法,應該確保首先調用super.init(),它保證ActionServlet的默認初始化操作被執行。除了覆蓋init()方法外,事實上,還可以根據十幾需要覆蓋ActionServlet的任何其他方法。

 

 

2 RequestProcessor

 

  對于多應用模塊的Struts應用,每個子應用模塊都有各自的RequestProcessor實例。在ActionServletprocess()方法中,一旦選擇了正確的子應用模塊,就會調用子應用模塊的RequestProcessor實例的process()方法來處理請求。在ActionServlet調用這個方法時,會把當前的requestresponse對象傳給它。

Struts框架只允許應用中存在一個ActionServlet類,但是可以存在多個客戶化的RequestProcessor類,每個子應用模塊都可以擁有單獨的RequestProcessor類。如果想修改RequestProcessor類的一些默認功能,可以覆蓋RequestProcessor基類中的相關方法。

 

(a)    RequestProcessor類的process()方法

RequestProcessor類的process()方法負責實際的預處理請求操作。

RequestProcessor類的process()方法一次執行一下流程:

(1) 調用processMultipart()方法。如果HTTP請求方式為POST,并且請求的contentType屬性以“multipart/form-data”開頭,標準的HttpServletRequest對象將被重新包裝,以方便處理“multipart”類型的HTTP請求。如果請求方式為GET,或者contentType屬性不是“multipart”,就直接返回原始的HttpServletRequest對象。

(2) 調用processPath()方法,獲得請求URI的路徑,這一信息可用于選擇合適的Struts Action組件。

(3) 調用processLocale()方法,當ControllerConfig對象的locale屬性為true,將讀取用戶請求中包含的Locale信息,然后把Locale實例保存在session范圍內。

(4) 調用processContent()方法,讀取ControllerConfig對象的contentType屬性,然后調用response.setContentType(contentType)方法,設置響應結果的文檔類型和字符編碼。

(5) 調用processNoCache()方法,讀取ControllerConfig對象的nocache屬性,如果nocache屬性為true,在響應結果中將加入特定的頭參數:PragmaCache-ControlExpires,防止頁面被存儲在客戶瀏覽器的緩存中。

(6) 調用processPreprocess()方法。該方法不執行任何操作,直接返回true。子類可以覆蓋這個方法,執行客戶化的預處理請求操作。

(7) 調用processMapping()方法,尋找和用戶請求的URI匹配的ActionMapping。如果不存在這樣的ActionMapping,則向用戶返回恰當的錯誤消息。

(8) 調用processRoles()方法,先判斷是否為Action配置了安全角色,如果配置了安全角色,就調用isUserRole()方法判斷當前用戶是否具備必需的角色;如果不具備,就結束請求處理流程,向用戶返回恰當的錯誤消息。

(9) 調用processActionForm()方法,先判斷是否為ActionMapping配置了ActionForm,如果配置了ActionForm,就先從ActionForm的存在范圍內尋找該ActionForm實例;如果不存在,就創建一個實例。接下來把它保存在合適的范圍中,保存時使用的屬性keyActionMappingname屬性。

(10) 調用processActionForm()方法。如果為ActionMapping配置了ActionForm,就先調用ActionFormreset()方法,再把請求中的表單數據組裝到ActionForm中。

(11) 調用processValidate()方法,如果為ActionMapping配置了ActionForm,并且ActionMappingvalidate屬性為true,就調用ActionFormvalidate()方法。如果validate()方法返回的ActionErrors對象中包含ActionMessage對象,說明表單驗證失敗,就把ActionErrors對象存儲在request范圍內,再把請求轉發到ActionMappinginput屬性指定的Web組件。如果ActionFormvalidate()方法執行表單驗證成功,就繼續執行下一步請求處理流程。

(12) 調用processForward()方法,判斷是否在ActionMapping中配置了forward屬性。如果配置了這個屬性,就調用RequestDispatcherforward()方法,請求處理流程結束,否則繼續下一步。

(13) 調用processInclude()方法,判斷是否在ActionMapping中配置了include屬性。如果配置了這個屬性,就調用RequestDispatcherinclude()方法,請求處理流程結束,否則繼續下一步。

(14) 調用processActionCreate()方法,先判斷是否在Action緩存中存在這個Action實例,如果不存在,就創建一個Action實例,把它保存在Action緩存中。

(15) 調用processActionPerform()方法,該方法再調用Action實例的execute()方法。execute()方法位于try/catch代碼中,以便捕獲異常。

(16) 調用processActionForward()方法,把Actionexecute()方法返回的ActionForward對象作為參數傳給它。processActionForward()根據ActionForward對象包含的請求轉發信息來執行請求轉發或重定向。

 

(b)    擴展RequestProcessor

開發人員可以很方便地創建客戶化的RequestProcessor類。

Struts配置文件中,<controller>元素的processorClass屬性RequestProcessor類。

 

 

3 Action

Action類是用戶請求和業務邏輯之間的橋梁。每個Action充當客戶的一項業務代理。在RequestProcessor類預處理請求時,在創建了Action的實例后,就調用自身的processActionPerform()方法,該方法再調用Action類的execute()方法。

Actionexecute()方法調用模型的業務方法,完成用戶請求的業務邏輯,然后根據執行結果把請求轉發給其他合適的Web組件。

 

(a)    Action類緩存

為了確保線程安全(thread-safe),在一個應用的生命周期中,Struts框架只會為每個Action類創建一個Action實例。所有的客戶請求共享一個Action實例,并且所有請求線程可以同時執行它的execute()方法。

RequestProcessor類包含一個HashMap,作為存放所有Action實例的緩存,每個Action實例在緩存中存放的屬性keyAction類名。在RequestProcessor類的processActionCreate()方法中,首先檢查在HashMap中是否存在Action實例,如果存在,就返回這個實例;否則,就創建一個新的Action實例。創建Action實例的代碼位于同步代碼塊中,以保證只有一個線程創建Action實例。一旦線程創建了Action實例并把它存放到HashMap中,以后所有的線程就會直接使用這個緩存中的實例。

 

(b)    ActionForward

Action類的execute()方法返回一個ActionForward對象。ActionForward對象代表了Web資源的邏輯抽象,這里的Web資源可以是JSP頁、Java ServletAction。從execute()方法中返回ActionForward對象有兩種方法:

· execute()方法中動態創建一個ActionForward實例

· Struts配置文件中配置<forward>元素。

       配置了<forward>元素后,在Struts框架初始化時就會創建存放<forward>元素;

   execute()方法中只需調用ActionMapping實例的findForward()方法,來獲取特定的ActionForward實例。findForward()方法先調用findForwardConfig()方法,在Action級別(即<action>元素內的<forward>子元素)尋找匹配的ActionForward對象。如果沒有,再在全局級別(即<global-forwards>元素內的<forward>子元素)中尋找匹配的ActionForward對象。如果找到,就返回該ActionForward對象。如果findForward()方法沒有找到匹配的ActionForward對象,它不會拋出異常,而是返回null。在瀏覽器端,用戶將收到一個空白頁。

采用第二種方式,無需在程序中硬編碼來指定轉發資源的無力路徑,而是在配置文件中配置轉發資源,程序中秩序引用轉發資源的邏輯名即可,這提高了應用的靈活性和可維護性。

 

(c)    創建支持多線程的Action

Struts應用的生命周期中,只會為每個Action類創建一個實例,所有的客戶請求共享這個實例。因此,必需保證在多線程環境中,Action也能正常工作。保證線程安全的重要元素是在Action類中僅僅使用局部變量,謹慎地使用實例變量。

如果在Actionexecute()方法中定義了局部變量,對于每個調用execute()方法的線程,Java虛擬機會在每個線程的堆棧中創建局部變量,因此每個線程擁有獨立的局部變量,不會被其他線程共享。當線程執行完execute()方法時,它的局部變量就會被銷毀。

如果在Action類中定義了實例變量,那么在Action實例的整個生命周期中,這個實例變量被所有的請求線程共享。因此不能在Action類中定義代表特定客戶狀態的實例變量。

Action類中定義的實例變量代表了可以被所有請求線程訪問的共享資源。為了避免共享資源的競爭,在必要的情況下,需要采用Java同步機制對訪問共享資源的代碼塊進行同步。

 

(d) Action類的安全

在某些情況下,如果Action類執行的功能非常重要,則只允許具有特定權限的用戶才能訪問該Action。為了防止未授權的用戶來訪問Action,可以在配置Action時指定安全角色。

<action>元素的roles屬性指定訪問這個Action的用戶必須具備的安全角色,多個角色之間以逗號隔開。

RequestProcessor類在預處理請求時會調用自身的processRoles()方法,該方法先檢查在配置文件中是否未Action配置了安全角色,如果配置了安全角色,就調用HttpServletRequestisUserInRole()方法,來判斷用戶是否具備了必要的安全角色。如果不具備,就直接向客戶端返回錯誤。

 

 

 

使用內置的Struts Action

 

Struts提供了一些現成的Action類,在Struts應用中直接使用這些Action類可以大大節省開發時間。

 

1 org.apache.struts.actions.ForwardAction

  JSP網頁中,盡管可以直接通過<jsp:forward>標簽把請求轉發給其他Web組件,但是Struts框架提倡先把請求轉發給控制器,再由控制器來負責請求轉發。

 

  有控制器來負責請求轉發有以下一些優點:

·控制器具有預處理請求功能,它能夠選擇正確的子應用模塊來處理請求,并且把子應用模塊的ModuleConfigMessageResources對象存放在request范圍內。這樣,請求轉發的目標Web組件就可以正常地訪問ModuleConfigMessageResources對象。

·如果JSP頁面中包含HTML表單,那么控制器能夠創建和這個表單對應的ActionForm對象,把用戶輸入表單數據組裝到ActionForm中。如果<action>元素的validate屬性為true,那么還會調用ActionForm的表單驗證方法。控制器把ActionForm對象存放在requestsession范圍內,這樣請求轉發的目標Web組件也可以訪問ActionForm

·JSP網頁之間直接相互轉發違背了MVC的分層原則,按照MVC設計思想,控制器負責處理所有請求,然后選擇適當的視圖組件返回給用戶,如果直接讓JSP相互調用,控制器就失去了流程控制作用。

 

  對于用戶自定義的Action類,既可以負責請求轉發,還可以充當客戶端的業務代理,如果僅僅需要Action類提供請求轉發功能,則可以使用org.apache.struts.actions.ForwardAction類。ForwardAction類專門用于轉發請求,不執行任何其他業務操作。

  ActionServlet把請求轉發給ForwardActionForwardAction再把請求轉發給<action>元素中parameter屬性指定的Web組件。總之,在Web組件之間通過ForwardAction類來進行請求轉發,可以充分利用Struts控制器的預處理請求功能。此外,也可以通過<action>元素的forward屬性來實現請求轉發。

 

2 org.apache.struts.actions.IncludeAction

  JSP網頁中,盡管可以直接通過<include>指令包含另一個Web組件,但是Struts框架提倡先把請求轉發給控制器,再由控制器來負責包含其他Web組件。IncludeAction類提供了包含其他Web組件的功能。與ForwardAction一樣,Web組件通過IncludeAction類來包含另一個Web組件,可以充分利用Struts控制器的預處理功能。

<action>paramter屬性指定需要包含的Web組件。此外,也可以通過<action>元素的include屬性來包含Web組件。

 

3 org.apache.struts.actions.DispatchAction

  通常,在一個Action類中,只能完成一種業務操作,如果希望在同一個Action類中完成一組相關的業務操作,可以使用DispatchAction類。

  創建一個擴展DispatchAction類的子類,不必覆蓋execute()方法,而是創建一些實現實際業務操作的方法,這些業務方法都應該和execute()方法具有同樣的方法簽名,即他們的參數和返回類型都應該相同,此外也應該聲明拋出Exception

  在配置DispatchAction類時,需要把parameter屬性設置為“method”。設置之后,當用戶請求訪問DispatchAction時,應該提供method請求參數。

 

4 org.apache.struts.actions.LookupDispatchAction

LookupDispatchAction類是DispatchAction的子類,在LookupDispatchAction類中也可以定義多個業務方法。通常LookupDispatchAction主要應用于在一個表單中有多個提交按鈕,而這些按鈕又有一個共同的名字的場合,這些按鈕的名字和具體的ActionMappingparameter屬性值相對應。

Struts配置文件中配置LookUpDispatchAction

<action>元素中,設置parameter屬性時,需要使它和<html:submit>標簽的property屬性保持一致。

 

5 org.apache.struts.actions.SwitchAction

SwitchAction類用于子應用模塊之間的切換。對于請求訪問SwitchActionURL,需要提供兩個參數:

·prefix:指定子應用模塊的前綴,以“/”開頭,默認子應用模塊的前綴為空字符串“”。

·page:指定被請求Web組件的URI,只需指定相對于被切換后的子應用模塊的相對路徑。

 

 

利用Token解決重復提交

 

  在某些情況下,如果用戶對同一個HTML表單多次提交,Web應用必需能夠判斷用戶的重復提交行為,以作出相應的處理。

  可以利用同步令牌(Token)機制來解決Web應用中重復提交的問題,Struts也給出了一個參考實現。org.apache.struuts.action.Action類中提供了一系列和Token相關的方法:

  ·protected boolean isTokenValid(javax.servlet.http.HttpServletRequest request)

判斷存儲在當前用戶會話中的令牌值和請求參數中的令牌值是否匹配。如果匹配,就返回true,否則返回false。只要符合一下情況之一,就返回false

·不能存在HttpSession對象。

·在session范圍內沒有保存令牌值。

·在請求參數中沒有令牌值

·存儲在當前用戶session范圍內的令牌值和請求參數中的令牌值不匹配。

  ·protected void resetToken(javax.servlet.http.HttpServletRequest request)

從當前session范圍內刪除令牌屬性。

  ·protected void saveToken(javax.servlet.http.HttpServletRequest request)

創建一個新的令牌,并把它保存在當前session范圍內。如果HttpSession對象不存在,就首先創建一個HttpSession對象。

 

  具體的Token處理邏輯由org.apache.struts.util.TokenProcessor類來完成,它的generateToken(request)方法根據用戶會話ID和當前系統時間來生成一個唯一的令牌。

 

 

實用類

 

  在創建Web應用時,有許多檢索和處理HTTP請求的操作時重復的。為了提高應用代碼的可重用性,減少冗余,Struts框架提供了一組提供這些通用功能的實用類,它們可以被所有的Struts應用共享。

 

1 RequestUtils

org.apache.struts.util.RequestUtilsStruts控制框架提供了一些處理請求的通用方法。RequestUtils類中的所有方法都是線程安全的,在這個類中沒有定義任何實例變量,所有的方法都被聲明為static類型。因此,不必創建RequestUtils類的實例,可以直接通過類名來訪問這些方法。

RequestUtils類的常用方法

方法

描述

absoluteURL(HttpServletRequest request,String utl)

創建并返回絕對URL路徑,參數path指定相對于上下文(context-relative)的相對路徑

createActionForm(HttpServletRequest request, ActionMapping mapping, ModuleConfig moduleConfig, ActionServlet servlet)

先從requestsession范圍內查找該ActionForm,如果存在,就直接將它返回,否則先創建它的實例,把它保存在requestsession范圍內,再把它返回。mapping參數包含了<action>元素的配置信息,例如它的scope屬性指定ActionForm的范圍

populate(Object bean, HttpServletRequest request)

HTTP請求中的參數值組裝到指定的JavaBean中,請求的參數名和JavaBean的屬性名匹配。當ActionServlet把用戶輸入的表單數據組裝到ActionForm中時,就調用此方法

 

2 TagUtils

org.apache.struts.taglib.TagUtil類為JSP標簽處理類提供了許多實例方法,如果要使用TagUtils類,首先應調用TagUtils.getInstance()方法,獲得TagUtils類的實例,getInstance()方法為靜態方法。

TagUtils類的常用方法

方法

描述

getInstance()

返回一個TagUtils的實例。該方法為靜態的,如果要在程序中獲得TagUtils的實例,可以調用TagUtils.getInstance()方法

getActionMessages(PageContext pageContext, String paramName)

調用pageContext.findAttribute(paramName)方法,從page, request, sessionapplication范圍內減縮并返回ActionMessages對象,參數paramName指定檢索ActionMessages對象的屬性key

getModuleConfig(PageContext pageContext)

返回ModuleConfig對象,如果不存在,就返回null

lookup(PageContext pageContext, String name, String scope)

返回特定范圍內的JavaBean。參數scope指定JavaBean的所在范圍,name參數指定JavaBean在特定范圍內的名字

message(PageContext pageContext, String bundle, String locale, String key)

從指定的Resource Bundle中返回一條消息文本,參數locale指定Locale,參數key指定消息key

write(PageContext pageContext, String text)

向網頁上輸入特定的文本,參數text用于指定文本內容

 

3 ModuleUtils

org.apache.struts.taglib.ModuleUtils類提供了處理子應用模塊的實用方法,如果要使用ModuleUtils類,首先應該調用ModuleUtils.getInstance()方法,獲得ModuleUtils類的實例,getInstance()方法為靜態方法。

ModuleUtils類的常用方法

方法

描述

getInstance()

返回一個ModuleUtils的實例。該方法為靜態的,如果要在程序中獲得ModuleUtils的實例,可以調用ModuleUtils.getInstance()方法。

getModuleConfig(javax.servlet.http.HttpervletRequest request)

request范圍內檢索并返回ModuleConfig對象

getModuleConfig(java.lang.String prefix, javax.servlet.ServletContext context)

application范圍內檢索并返回ModuleConfig對象,參數prefix指定子應用模塊名的前綴

getModuleName(javax.servlet.http.HttpServletRequest request, javax.servlet.ServletContext context)

返回請求訪問的子應用模塊的名字

selectModule(javax.servlet.http.HttpServletRequest request, javax.servlet.ServletContext context)

選擇請求訪問的子應用模塊,把和子應用模塊相關的ModuleConfigMessageResources對象存儲到request范圍中

 

4 Globals

org.apache.struts.Globals類提供一組公共類型的靜態常量,被用作在特定范圍內存放JavaBean的屬性key

Globals類中定義的常量

方法

描述

ACTION_SERVLET_KEY

代表在application范圍內存放ActionServlet實例的屬性key

DATA_SOURCE_KEY

代表在application范圍內存放默認的DataSource實例的屬性key

ERROR_KEY

代表在request范圍內存放ActionErrors實例的屬性key

LOCALE_KEY

代表在session范圍內存放Locale實例的屬性key

MAPPING_KEY

代表在request范圍內存放ActionMapping實例的屬性key

MESSAGE_KEY

代表在request范圍內存放ActionMessages實例的屬性key

MESSAGES_KEY

代表在application范圍內存放各個子應用模塊的MessageResources實例的屬性key的前綴

MODULE_KEY

代表在application范圍內存放各個子應用模塊的ModuleConfig實例的屬性key的前綴

REQUEST_PROCESSOR_KEY

代表在application范圍內存放各個子應用模塊的RequestProcessor實例的屬性key的前綴



閱讀材料:《精通Struts:基于MVC的Java Web設計與開發》





                                         2005年05月06日 4:05 PM