一、概述
在Struts 架構中,Controller主要是ActionServlet,但是對於業務邏輯的操作則主要由Action、ActionMapping、ActionForward這幾個組件協調完成。其中,Action扮演了真正的業務邏輯的實現者,而ActionMapping和ActionForward則指定了不同業務邏輯或流程的運行方向。
應用程序的 Controller 部分集中於從客戶端接收請求(典型情況下是一個運行浏覽器的用戶),決定執行什麼商業邏輯功能,然後將產生下一步用戶界面的責任委派給一個適當的View組件。在Struts中,controller的基本組件是一個 ActionServlet 類的servlet。這個servlet通過定義一組映射(由Java接口 ActionMapping 描述)來配置。每個映射定義一個與所請求的URI相匹配的路徑和一個 Action 類(一個實現 Action 接口的類)完整的類名,這個類負責執行預期的商業邏輯,然後將控制分派給適當的View組件來創建響應。
Struts也支持使用包含有運行框架所必需的標准屬性之外的附加屬性的 ActionMapping 類的能力。這允許我們保存特定於我們的應用程序的附加信息,同時仍可利用框架其余的特性。另外,Struts允許我們定義控制將重定向到的邏輯名,這樣一個行為方法可以請求"主菜單"頁面,而不需要知道相應的JSP頁面的實際名字是什麼。這個功能極大地幫助我們分離控制邏輯(下一步做什麼)和顯示邏輯(相應的頁面的名稱是什麼)。下圖1是Struts的controller組件示意圖:
二、創建Controller組件
Struts包括一個實現映射一個請求URI到一個行為類的主要功能的servlet。因此我們的與Controller有關的主要責任是:
為每一個可能接收的邏輯請求寫一個 Action 類(也就是,一個 Action 接口的實現);寫一個定義類名和與每個可能的映射相關的其它信息的 ActionMapping 類(也就是,一個 ActionMapping 接口的實現);寫行為映射配置文件(用XML)用來配置controller servlet。
為應用程序更新web應用程序展開描述符文件(用XML)用來包括必需的Struts組件,我們給應用程序添加適當的Struts組件。
1、Action 實現
Action 接口定義一個單一的必須由一個 Action 類實現的方法,就象下面這樣:
public ActionForward perform(ActionServlet servlet,
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
一個 Action 類的目標是處理這個請求,然後返回一個標識JSP頁面的 ActionForward 對象,控制應該重定向這個JSP頁面以生成相應的響應。Struts 架構為應用系統中的每一個Action類只創建一個實例。因為所有的用戶都使用這一個實例,所以你必須確定你的Action 類運行在一個多線程的環境中。下圖2顯示了一個execute()方法如何被訪問:
圖2 Action實例的execute()方法
注意,客戶自己繼承的Action子類,必須重寫execute()方法,因為Action類在默認情況下是返回null的。
在 Model 2 設計模式中,一個典型的 Action 類將在它的 perform() 方法中實現下面的邏輯:
驗證用戶session的當前狀態(例如,檢查用戶已經成功地注冊)。如果 Action 類發現沒有注冊存在,請求應該重定向到顯示用戶名和口令用於注冊的JSP頁面。應該這樣做是因為用戶可能試圖從"中間"(也就是,從一個書簽)進入我們的應用程序,或者因為session已經超時並且servlet容器創建了一個新的session。如果驗證還沒有發生(由於使用一個實現 ValidatingActionForm 接口的form bean),驗證這個 form bean 的屬性是必須的。如果發現一個問題,當作一個請求屬性保存合適的出錯信息關鍵字,然後將控制重定向回輸入表單這樣錯誤可以被糾正。
執行要求的處理來處理這個請求(例如在數據庫裡保存一行)。這可以用嵌入 Action 類本身的代碼來完成,但是通常應該調用一個商業邏輯bean的一個合適的方法來執行。更新將用來創建下一個用戶界面頁面的服務器端對象(典型情況下是request范圍或session范圍beans,定義我們需要在多長時間內保持這些項目可獲得)。返回一個標識生成響應的JSP頁面的適當的 ActionForward 對象,基於新近更新的beans。典型情況下,我們將通過接收到的 ActionMapping 對象(如果我們使用一個局部於與這個映射上的邏輯名)或者在controller servlet 本身(如果我們使用一個全局於應用程序的邏輯名)上調用 findForward() 得到一個對這樣一個對象的引用。
當為 Action 類編程時要記住的設計要點包括以下這些:
controller servlet僅僅創建一個我們的 Action 類的實例,用於所有的請求。這樣我們需要編寫我們的 Action 類使其能夠在一個多線程環境中正確運行,就象我們必須安全地編寫一個servlet的 service() 方法一樣。
幫助線程安全編程的最重要的原則就是在我們的 Action 類中僅僅使用局部變量而不是實例變量。局部變量創建於一個分配給每個請求線程的棧中,所以沒有必要擔心會共享它們。
盡管不應該,代表我們的系統中Model部分的的beans仍有可能拋出違例。我們應該在我們的 perform() 方法的邏輯中捕捉所有這樣的違例,並且通過執行以下語句將它們記錄在應用程序的日志文件中(包括相應的棧跟蹤信息):
servlet.log("Error message text", exception);
作為一個通用的規則,分配很少的資源並在來自同一個用戶(在用戶的session中)的請求間保持它們會導致可伸縮性的問題。另外,我們將會想要防止出現非常大的 Action 類。最簡單的實現途徑是將我們的功能邏輯嵌入到 Action 類本身,而不是將其寫在獨立的商業邏輯beans中。除了使 Action 類難於理解和維護外,這種方法也使得難於重用這些商業邏輯代碼,因為代碼被嵌入到一個組件(Action 類)中並被捆綁運行於web應用程序環境中。
包括在Struts中的例子程序某種程度上延伸了這個設計原則,因為商業邏輯本身是嵌入到 Action 類中的。這應該被看作是在這個樣本應用程序設計中的一個bug,而不是一個Struts體系結構中的固有特性,或者是一個值得仿效的方法。
2、ActionMapping實現
為了成功地運行,Struts的controller servlet需要知道關於每個URI該怎樣映射到一個適當的 Action 類的幾件事。需要了解的知識封裝在一個叫做 ActionMapping 的Java接口中,它有以下屬性:
actionClass :用於這個映射的 Action 類完整的Java類名。第一次一個特定的映射被使用,一個這個類的實例將被創建並為以後重用而保存。
formAttribute :session范圍的bean的名字,當前的這個映射的 ActionForm 被保存在這個bean之下。如果這個屬性沒有被定義,沒有 ActionForm 被使用。
formClass :用於這個映射的 ActionForm 類完整的Java類名。如果我們在使用對form beans的支持,這個類的一個實例將被創建並保存(在當前的用戶會話中)
path :匹配選擇這個映射的請求的URI路徑。看下面如何匹配的例子。
Struts在一個叫做 ActionMappingBase 的類中包括了一個 ActionMapping 接口的方便的實現。如果我們不需要為我們自己的映射定義任何附加的屬性,盡管把這個類作為我們的 ActionMapping 類好了,就向下面部分描述的那樣配置。然而,定義一個 ActionMapping 實現(多半是擴展 ActionMappingBase 類)來包含附加的屬性也是可能的。controller servlet知道怎樣自動配置這些定制屬性,因為它使用Struts的Digester模塊來讀配置文件。
包括在Struts的例子程序中,這個特性用來定義兩個附加的屬性:
failure :如果Action類檢測到它接收的輸入字段的一些問題,控制應該被重定向到的上下文相關的URI。典型情況下是請求發向的JSP頁面名,它將引起表單被重新顯示(包含Action類設置的出錯消息和大部分最近的來自ActionForm bean的輸入值)。
success :如果Action類成功執行請求的功能,控制應該被重定向到的上下文相關的URI。典型情況下是准備這個應用程序的會話流的下一個頁面的JSP頁面名。
使用這兩個額外的屬性,例子程序中的 Action 類幾乎完全獨立於頁面設計者使用的實際的JSP頁面名。 這個頁面可以在重新設計時被重命名,然而幾乎不會影響到 Action 類本身。如果"下一個"JSP頁面的名字被硬編碼到 Action 類中,所有的這些類也需要被修改。
3、ActionForward實現
目的是控制器將Action類的處理結果轉發至目的地。
Action類獲得ActionForward實例的句柄,然後可用三種方法返回到ActionServlet,所以我們可以這樣使用ActionForward():ActionServlet根據名稱獲取一個全局轉發;ActionMappin實例被傳送到perform()方法,並根據名稱找到一個本地轉發。
另一種是調用下面的一個構造器來創建它們自己的一個實例:
public ActionForward()
public ActionForward(String path)
public ActionForward(String path,Boolean redirect)
4、Action映射配置文件
controller servlet怎樣知道我們想要得到的映射?寫一個簡單地初始化新的 ActionMapping 實例並且調用所有適當的set方法的小的Java類是可能的(但是很麻煩)。為了使這個處理簡單些,Struts包括一個Digester模塊能夠處理一個想得到的映射的基於XML的描述,同時創建適當的對象。
開發者的責任是創建一個叫做 action.xml 的XML文件,並且把它放在我們的應用程序的WEB-INF目錄中。(注意這個文件並不需要 DTD,因為實際使用的屬性對於不同的用戶可以是不同的)最外面的XML元素必須是<action-mappings>,在這個元素之中是嵌入的0個或更多的 <action> 元素 -- 每一個對應於我們希望定義的一個映射。
來自例子程序的 action.xml 文件包括"注冊"功能的以下映射條目,我們用來說明這個需求:
<action-mappings>
<forward name="logon" path="/logon.jsp"/>
<action path="/logon" actionClass="org.apache.struts.example.LogonAction"
formAttribute="logonForm" formClass="org.apache.struts.example.LogonForm" inputForm="/logon.jsp">
<forward name="success" path="/mainMenu.jsp"/>
</action>
</action-mappings>
就象我們所看到的,這個映射匹配路徑 /logon (實際上,因為例子程序使用擴展匹配,我們在一個JSP頁面指定的請求的URI結束於/logon.do)。當接收到一個匹配這個路徑的請求時,一個 LogonAction 類的實例將被創建(僅僅在第一次)並被使用。controller servlet將在關鍵字 logonForm 下查找一個session范圍的bean,如果需要就為指定的類創建並保存一個bean。
這個 action 元素也定義了一個邏輯名"success",它在 LogonAction 類中被用來標識當一個用戶成功注冊時使用的頁面。象這樣使用一個邏輯名允許將 action 類隔離於任何由於重新設計位置而可能發生的頁面名改變。
這是第二個在任何 action 之外宣告的 forward 元素,這樣它就可以被所有的action全局地獲得。在這個情況下,它為注冊頁面定義了一個邏輯名。當我們調用 mapping.findForward() 時在我們的 action 代碼中,Struts首先查找這個action本地定義的邏輯名。如果沒有找到,Struts會自動為我們查找全局定義的邏輯名。
5、Web應用程序展開描述符
設置應用程序最後的步驟是配置應用程序展開描述符(保存在文件WEB-INF/web.xml中)以包括所有必需的Struts組件。作為一個指南使用例子程序的展開描述符,我們看到下面的條目需要被創建或修改。
1)配置ActionServlet實例
添加一個條目定義actionservlet本身,同時包括適當的初始化參數。這樣一個條目看起來象是這樣:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>org.apache.struts.example.ApplicationResources</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/action.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>mapping</param-name>
<param-value>org.apache.struts.example.ApplicationMapping</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
controller servlet支持的初始化參數在下面描述,拷貝自 ActionServlet 類的 Javadocs 。方括號描述如果我們沒有為那個初始化參數提供一個值時假設的缺省值。
application :應用程序資源包基類的Java類名。[NONE]
config :包含配置信息的XML資源的上下文相關的路徑。[/WEB-INF/action.xml]
debug :這個servlet的調試級別,它控制記錄多少信息到日志中。[0]
digester : 我們在 initMapping() 中利用的Digester的調試級別,它記錄到System.out而不是
servlet的日志中。[0]
forward :使用的ActionForward實現的Java類名。[org.apache.struts.action.ActionForward]
mapping :使用的ActionMapping實現的Java類名。[org.apache.struts.action.ActionMappingBase]
nocache : 如果設置為 true,增加HTTP頭信息到所有響應中使浏覽器對於生成或重定向到的任何響應不做緩沖。[false]
null :如果設置為 true,設置應用程序資源使得如果未知的消息關鍵字被使用則返回 null。否則,一個包括不歡迎的消息關鍵字的出錯消息將被返回。[true]
2)配置ActionServlet映射
有兩種通常的方法來定義將被controller servlet處理的URL:前綴匹配和擴展匹配。每種方法的一個適當的映射條目將在下面被描述。
前綴匹配意思是我們想讓所有以一個特殊值開頭(在上下文路徑部分之後)的URL傳遞給這個servlet。這樣一個條目看起來可以象是這樣:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/execute/*</url-pattern>
</servlet-mapping>
它意味著一個匹配前面描述的 /logon 路徑的請求的URL看起來象是這樣:
http://www.mystudy.com/myapplication/execute/logon
這裡 /myapplication是我們的應用程序展開所在的上下文路徑。
另一方面,擴展映射基於URL以一個跟著定義的一組字符的句點結束的事實而將URL匹配到action servlet 。例如,JSP處理servlet映射到 *.jsp 模式這樣它在每個JSP頁面請求時被調用。為了使用 *.do 擴展(它意味著"做某件事")映射條目看起來應該象是這樣:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
並且一個匹配以前描述的 /logon 路徑的請求的URI可以看起來象是這樣:
http://www.mystudy.com/myapplication/logon.do
3)配置Struts標記庫
下一步,我們必須添加一個定義Struts標記庫的條目。這個條目看起來應該象是這樣:
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.tld</taglib-location>
</taglib>
它告訴JSP系統到哪裡去找這個庫的標記庫描述符(在我們的應用程序的WEB-INF目錄)。
4)添加Struts組件到我們的應用程序中
為了在我們的應用程序運行時使用Struts,我們必須將 struts.tld 文件拷貝到我們的 WEB-INF 目錄,將struts.jar 文件拷貝到我們的 WEB-INF/lib 。