一、概述
Model就是在對用戶請求的整個控制過程中,真正處理用戶請求並保存處理結果的對象,在整個過程中,我們一般利用JavaBean來把一些信息保存起來以便在各個對象之間傳遞。因為在框架中,Model對象是真正處理商業邏輯功能的對象,因此也就是框架中應用需求實現相關性最大的部分。 在Struts的實現裡,Model的具體表現形式就是ActionForm對象和與其對應的Action對象了。對用戶提交表單的數據進行校驗,甚至對數據進行預處理都能在ActionForm中完成。通常的應用中,一般是一個Model對象和一個請求頁面對應的關系,但也可以一個Model對象對應多個頁面請求。如果struts-config.xml配置文件沒有指定一個Model對象對應的Action,那麼控制器將直接把(通過Model對象完成數據封裝的)請求轉到一個View對象。下圖表示的是Model layer 的層次結構。
在Struts中Model以一個或多個java bean的形式存在。這些bean分為三類:Action Form、Action、JavaBean or EJB。Action Form通常稱之為FormBean,封裝了來自於Client的用戶請求信息,如表單信息。Action通常稱之為ActionBean,獲取從ActionSevlet傳來的FormBean,取出FormBean中的相關信息,並做出相關的處理,一般是調用JavaBean或EJB等。
許多需求文檔將構建Web應用的焦點集中在視圖上。我們必須確保每一個提交的請求都在模型視圖中都已經被定義。通常,開發者在模型組件中關注於開發JavaBean類以實現所有的功能需求。 應用應該准確的使用哪些beans,根據其需求不同而差異巨大,但是,在經過區分後通常都能分被為若干個類別。
二、創建Model 組件
1、JavaBeans
在一個Web基礎的應用中,能使用許多不同的"屬性(attributes)"集合來保存(和訪問)JavaBeans。 每個集合都有它自己不同的生命周期和beans存儲在哪裡的可見度。 同時,beans通過 作用域 來定義生命周期和可見度規則。 在JavaServer Pages (JSP)規范中定義了作用域選擇使用以下幾項(在括號中是servlet API中的等價概念定義)。
page :Beans只會在一個JSP頁中可見,只在當前的請求周期中存在。 (在 service 方法中的本地變量)
request : Beans只會在一個JSP頁中可見,與page相同或servlet包含本頁,或轉發到本頁。 (Request屬性)
session :Beans能被所有的JSP頁和servlet通過特定的用戶session來使用, 它可以跨越一個或多個請求。 (Session屬性)
application :Beans能被Web應用中的所有JSP頁和servlets來使用。 (Servlet context屬性)
我們需要記住的是在一個web應用中JSP頁面和servlet會共享bean集合的設置。 例如在一個servlet中將一個bean存儲到attribute中如下:
MyStudy Mystudy = new MyStudy(...);
request.setAttribute("cart", MyStudy);
在這個servlet將請求轉發給一個JSP頁面後,我們馬上可以使用標准的動作標簽(tag)來看到相應的值:
<jsp:useBean id="cart" scope="request" class="com.mycompany.MyApp.MyStudy"/ >
2、ActionForm Beans
在 actionform beans頻繁地有屬性相當於屬性在我們的model beans的時候,那form beans它們自己應該考慮成為一個控制器組件。 同樣地,他們能在模型和視圖層之間傳遞資料。
Struts框架通常假定我們在我們的應用中已經為輸入定義一個 ActionForm beans(簡而言之,一個擴展自 ActionForm 類的Java類)。 ActionForm beans有時僅僅調用表單beans(form beans)。 這可能會是一個細粒度的對像,它讓每個表單對應一個bean,還有就是一個bean服務於若干個表單甚至全部應用形成粗粒度的情況。
如果在我們的Struts配置文件中定義了bean,Struts的controller servlet在調用適當的 Action 方法前將自動為我們提供如下服務:
使用適當的關鍵字檢查在用戶適當的作用域(request或session)中是否有適當類的bean的一個實例。
如果沒有這樣的實例可用,則自動建立一個新的bean實例並將期加入到適當的作用域中(request或session)。
對於每個請求參數通過其名稱來對應到bean的一個屬性(property)上,並調用相應的setter方法來設置屬性值。 這個方法類似於標准JSP中以以通配符"*"來使用 <jsp:setProperty > 標記。
更新後的 ActionForm bean被傳遞給 Action 類[ org.apache.struts.Action ] 的 execute 方法, 以使這些值能被我們的系統狀態和業務邏輯bean來使用。
我們應該注意一個"表單(form)"在這裡並不是必須對應於用戶界面中一個單獨的JSP頁面。 在很多應用程序中一個"表單"(從用戶的觀點)延伸至多個頁面也是很平常的。 想想看,例如,在新程序的安裝時所使用的導航程序的用戶界面。 Struts鼓勵我們定義一個包含所有字段屬性的單獨的 ActionForm bean, 而不用管這些字段實際顯示於哪個頁面上。同樣的,同一表單的不同頁面應提交到相同的Action類。 如果我們遵照這個建議,在大多數情況下,頁面設計者可以重新組織不同頁面中的字段而不需要改變處理邏輯。
一個小的應用也許只需要一個ActionForm來為所有的輸入表單提供服務。 其它應用可以為每個大的子系統來分別使用一個ActionForm。 還有一些人可能更喜歡為每一個輸入表單或工作流分別使用不同的ActionForm類。 真正如何使用ActionForm完全在於我們,框架自身並不在意的。
ActionForm 接口本身不需要特殊的實現方法。它是用來標識這些特定的beans在整個體系結構中的作用。典型情況下,一個 ActionForm bean只包括屬性的get方法和set方法,沒有商業邏輯。
通常在一個 ActionForm bean中只有很少的輸入驗證邏輯。這樣的beans存在的主要理由是保存用戶為相關的表單所輸入的大部分近期值,這樣同樣的頁面可以被重建,伴隨有一組出錯信息,這樣用戶僅僅需要糾正錯誤的字段。用戶輸入的驗證應該在 Action 類中執行(如果是很簡單的話),或者在適當的商業邏輯beans中執行。
為每個表單中出現的字段定義一個屬性(用相關的getXxx()和setXxx()方法)。字段名和屬性名必須按照JavaBeans的約定相匹配。例如,一個名為 username 的輸入字段將引起 setUsername() 方法被調用。
下面是ActionForm類的具體描述:
ActionForm類
框架假設用戶在應用程序中為每個表單都創建了一個ActionForm bean,對於每個在struts-config.xml文件中定義的bean,框架在調用Action類的perform()方法之前會進行以下操作:
1、在相關聯的關鍵字下,它檢查用於適當類的bean實例的用戶會話,如果在會話中沒有可用的bean,它就會自動創建一個新的bean並添加到用戶的會話中。
2、對於請求中每個與bean屬性名稱對應的參數,Action調用相應的設置方法。
3、當Action perform()被調用時,最新的ActionForm bean傳送給它,參數值就可以立即使用了。
ActionForm類擴展org.apache.struts.action.ActionForm類,程序開發人員創建的bean能夠包含額外的屬性,而且ActionServlet可能使用反射(允許從已加載的對象中回收信息)訪問它。
ActionForm類提供了另一種處理錯誤的手段,提供兩個方法:
Public ActionErrors validate(ActionMappin mapping,ServletRequest request)
Public ActionErrors validate(ActionMappin mapping,HttpServletRequest request)
我們應該在自己的bean裡覆蓋validate()方法,並在配置文件裡設置<action>元素的validate為true。在ActionServlet調用Action類前,它會調用validate(),如果返回的ActionErrors不是null,則ActinForm會根據錯誤關鍵字將ActionErrors存儲在請求屬性列表中。
如果返回的不是null,而且長度大於0,則根據錯誤關鍵字將實例存儲在請求的屬性列表中,然後ActionServlet將響應轉發到配置文件<action>元素的input屬性所指向的目標。
如果需要執行特定的數據有效性檢查,最好在Action類中進行這個操作,而不是在ActionForm類中進行。
方法reset()可將bean的屬性恢復到默認值:
public void reset(ActionMapping mapping,HttpServletRequest request)
public void reset(ActionMapping mapping,ServletRequest request)
典型的ActionFrom bean只有屬性的設置與讀取方法(getXXX),而沒有實現事務邏輯的方法。只有簡單的輸入檢查邏輯,使用的目的是為了存儲用戶在相關表單中輸入的最新數據,以便可以將同一網頁進行再生,同時提供一組錯誤信息,這樣就可以讓用戶修改不正確的輸入數據。而真正對數據有效性進行檢查的是Action類或適當的事務邏輯bean。
3、系統狀態Beans
系統的實際狀態通常表示為一組一個或多個JavaBean類,其屬性定義了當前的狀態。 例如,在一個購物車系統中,將包括一個表示購物車的bean,這個bean為每個購物者所維護, 它包括了購物者所選擇購買的物品條目。 另外,系統也包括保存用戶信息(包括他們的信用卡和送貨地址)、可獲得的條目和當前庫存水平這些不同的bean。
對於小規模系統,或是對於不需要長時間保存的狀態信息,一組系統狀態bean可以包含 所有系統曾經經歷的特定細節的信息。 或者經常是,系統狀態bean會表示永久保存在一些外部數據庫中的信息(例如 CustomerBean 對象對應於CUSTOMERS表中特定的一行數據), 在需要時從服務器的內存中創建或清除。Entity Enterprise JavaBeans也是用於這種用途的。
4、商業邏輯Beans
我們應該把用程序中的功能邏輯在設計時封裝成為JavaBean的方法調用。 這些方法可以是用於系統系統裝態bean相同的類中的一部分,或者可以是在專門執行商業邏輯的獨立的類中。 在後一種情況下,我們通常需要將系統狀態bean傳遞給這些方法做為參數以進行處理。
為了達到代碼的最大可重用性,商業邏輯bean應該被設計和實現為它們並 不 知道自己被執行於web應用環境中。 如果發現在我們的bean中正在import javax.servlet.* 中的類,我們就把這個商業邏輯限定在了web應用環境中。 應考慮重新組織我們的 Action 類通過對商業邏輯beans屬性的set方法的調用以翻譯所有從HTTP請求中發出的請示信息,之後再發出一個對 execute 方法的調用。 這樣才可以讓商業邏輯類在最初被構造的web應用程序以外的環境中被重用。
依賴於我們的應用程序的復雜度和范圍,商業邏輯beans可以是與作為參數傳遞的系統狀態bean交互作用的普通JavaBean, 或都是使用JDBC訪問數據庫的普通的JavaBean。而對於較大的應用程序,這些bean經常是有狀態或無狀態的Enterprise JavaBeans (EJBs)。
下面是Action 類的具體描述:
Action類
Action類真正實現應用程序的事務邏輯,它們負責處理請求。在收到請求後,ActionServlet會:
為這個請求選擇適當的Action
如果需要,創建Action的一個實例
調用Action的perform()方法
如果ActionServlet不能找到有效的映射,它會調用默認的Action類(在配置文件中定義)。如果找到了ActionServlet將適當的ActionMapping類轉發給Action,這個Action使用ActionMapping找到本地轉發,然後獲得並設置ActionMapping屬性。根據servlet的環境和被覆蓋的perform()方法的簽名,ActionServlet也會傳送ServletRequest對象或HttpServletRequest對象。
所有Action類都擴展org.apache.struts.action.Action類,並且覆蓋類中定義的某一個perform()方法。有兩個perform()方法:
處理非HTTP(一般的)請求:
public ActionForward perform(ActionMapping action,AcionForm form,ServletRequest request,
ServletResponse response)
throws IOException,ServletException
處理HTTP請求:
public ActionForward perform(ActionMapping action,AcionForm form,HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException
Action類必須以"線程安全"的方式進行編程,因為控制器會令多個同時發生的請求共享同一個實例,相應的,在設計Action類時就需要注意以下幾點:
不能使用實例或靜態變量存儲特定請求的狀態信息,它們會在同一個操作中共享跨越請求的全局資源
如果要訪問的資源(如JavaBean和會話變量)在並行訪問時需要進行保護,那麼訪問就要進行同步
Action類的方法
除了perform()方法外,還有以下方法:
可以獲得或設置與請求相關聯的區域:
public Locale getLocale(HttpServletRequest request)
public void setLocale(HttpServletRequest request,Locale locale)
為應用程序獲得消息資源:
public MessageResources getResources()
檢查用戶是否點擊表單上的"取消"鍵,如果是,將返回true:
public Boolean isCancelled(HttpServletRequest request)
當應用程序發生錯誤時,Action類能夠使用下面方法存儲錯誤信息:
public void saveErrors(HttpServletRequest request,ActionErrors errors)
ActionError實例被用來存儲錯誤信息,這個方法在錯誤關鍵字下的請求屬性列表中存儲ActionError對象。
5、訪問關系數據庫
很多web應用程序利用一個關系數據庫(通過一個JDBC driver訪問)來保存應用程序相關的永久數據。 其它應用程序則使用Entity EJBs來實現這個目的,他們委派EJBs自己來決定怎樣維護永久狀態。如果我們是使用EJBs來實現這個目的,遵照EJB規范中描述的客戶端設計模式。
對於基於直接數據庫訪問的web應用程序,一個普通的設計問題是當需要訪問低層數據庫時怎樣產生一個適當的JDBC連接對象。解決這個問題有方法如下:
創建或得到一個允許一組數據庫連接被多個用戶共享的ConnectionPool類。Struts(當前)沒有包括這樣的一個類,但是有很多這樣的類可以得到。
當應用程序初始化時,在應用程序展開(deployment)描述符中定義一個有一個"啟動時加載"值的servlet。我們將把這個servlet叫做 啟動 servlet。在大多數情況下,這個servlet不需要處理任何的請求,所以沒有一個<servlet-mapping> 會指向它。
在啟動servlet的 init() 方法中,配置並初始化一個ConnectionPool類的實例,將其保存為一個servlet context屬性(從JSP的觀點看等同於一個application范圍的bean)。通常基於傳遞給啟動servlet初始化參數來配置聯接緩沖池是很方便的。
在啟動servlet的 destroy() 方法中,包含了釋放聯接緩沖池所打開的聯接的邏輯。這個方法將在servlet容器結束這個應用程序的時候被調用。
當 Action 類需要調用一個需要數據庫聯接的商業邏輯bean中的方法(例如"insert a new customer")時,將執行下面的步驟:
1=為這個web應用程序從servelt context屬性中得到一個聯接緩沖池對象。
2=調用聯接緩沖池對象的 open() 方法來得到一個在 Action 類調用中使用的聯接。
3=調用商業邏輯bean中合適的方法,將數據庫聯接對象作為一個參數傳遞給它。
4=調用分配的聯接中的 close() 方法,這將引起這個聯接為了以後其它請求的重用被返回到緩沖池中。