第五章 LPMT中的控制結構
在 Struts 的控制邏輯中, Controller 的 ActionServlet 、 RequestProcessor 是由 Struts 自身實現的。用戶需要實現的是 Model 的 Action 的 Perform 或者 Execute 方法。我們定義了兩個 Action : IssueAction 和 MainAction ,在 Action 中根據參數 action 的類型判斷并實現各種邏輯操作。下面以 IssueAction 、 IssueActionForm 、 IssueDetail.jsp 為例說明 LPMT 中的控制結構實現。
5.1 Action中的控制結構實現
在 IssueAction 中,定義參數 action 來判斷各種操作類型。 Action 值來自客戶端請求的 URL , action = httpServletRequest.getParameter("action").trim() 。
action == “view” :分頁查看所有 Issue
action == “viewDetail” : 查看單個 Issue 及其 IssueDatas 和 ChangeLogs
action == “create” : 新建一個 Issue
action == “edit” :編輯選定 Issue
action == “delete” :刪除選定 Issue
action == “save” :保存新建或修改過的 Issue
action == ” createIssueData” :為選定的 Issue 新建 IssueData
action == “saveIssueData” :保存新建或修改過的 IssueData
action == “editIssueData” :修改選定的 IssueData
action == “deleteIssueData” :刪除選定的 IssueData
在判定操作類型之后, Action 根據需要初始化對象,調用 IssueBean 的方法完成相應的業務邏輯操作,最后返回相應的 ActionMapping 對象。
5.2 ActionForm中的控制邏輯實現
在 IssueActionForm 中,處理對應表單頁面的所有項之外,我們另外定義了一個 actionType 參數,來保存并傳遞 Action 的操作類型。
private String actionType = "create";
在 Action 判定操作類型之后,要設置對應 IssueActionForm 對象的 actionType 值。比如:
issueActionForm.setActionType(action);
issueDataActionForm.setActionType(action);
5.3 View中的控制邏輯實現
在 IssueAction 對應的 IssueDetail.jsp 頁面中,需要根據當前 IssueActionForm 參數 actionType 的值來判斷當前操作類型,繼而實現相應的顯示邏輯。比如:
當 actionType = “create” 時,操作類型為新建 Issue ,此時顯示一張空白表單,初始化 Component 、 Environment 等可選項;當 actionType = “edit” 時,操作類型為編輯 Issue ,此時要載入表單值,如 issueId , title , Component 等等;當 actionType = “viewDetail” 時,操作類型為查看 Issue 內容。這里使用 <logic:equal> 標簽來實現控制顯示邏輯,用 <logic:present> 和 <logic:notPresent> 判斷對應的 issueActionForm 是否已經載入頁面。我們以顯示 IssueId 為例:
?????? <logic:equal name="issueActionForm" property="actionType" value="create">
????????????? <html:text property="issueId"/>
?????? </logic:equal>
?????? <logic:equal name="issueActionForm" property="actionType" value="edit">
????????????? <logic:present name="issueActionForm">
???????????????????? <html:text name="issueActionForm" property="issueId"/>
????????????? </logic:present>
????????????? <logic:notPresent name="issueActionForm">
????????????? ?????? <b>Error:No issue.</b>
????????????? </logic:notPresent>
?????? </logic:equal>
?????? <logic:equal name="issueActionForm" property="actionType" value="viewDetail">
????????????? <logic:present name="issueActionForm">
???????????????????? <bean:write name="issueActionForm" property="issueId" />
????????????? </logic:present>
????????????? <logic:notPresent name="issueActionForm">
???????????????????? <b>Error:No issue.</b>
????????????? </logic:notPresent>
?????? </logic:equal>
頁面效果如下:
第六章 LPMT中的WebForm技術實現
在 Web 系統設計中, WebForm 作為直接呈現給客戶端的 View ,其實現技術一直為業界所關注。從最初的單純的 HTML ,到后來的 CGI 、 PERL ,到最近的 ASP 、 JSP ,到目前的標簽庫,業界一直在尋找一種能夠快速開發、靈活、高效率、可重用的技術來實現業務邏輯呈現。在 LPMT 中,我們采用 HTML 結合 Jakarta Struts 標簽的技術來實現, JSP 頁面作為單純的 View 使用,而業務邏輯完全由 Action 完成。同樣是運行于 Server 端的技術,與單純的 ASP 、 JSP 技術相比,運用 Jakarta Struts 標簽庫構建的 JSP 頁面結構更清晰,可重用性和擴展性更優秀。
6.1 Jakarta Struts 標簽庫
Jakarta Struts1.0 的標簽庫包括:
l???????
Bean
標簽:
提供一組操作標簽,用于在必要的時候封裝邏輯,使用JavaBeans、HTTP Cookies、HTTP Headers。這些標簽封裝在文件名為struts-bean.tld的標簽包中。
l???????
Logical
標簽:
提供一組用來在JSP頁中有條件地產生輸出文本,在對象集合中循環從而重復地產生輸出文本,以及應用程序流程控制的標簽。Logical標簽庫定義的標記能夠執行條件邏輯、重復循環、轉發/重定向等功能,可以完全替代scriptlet。這些標簽封裝在文件名為struts-logic.tld的標簽包中。
l???????
HTML
標簽:
用來生成HTML標簽,顯示表單元素、控件及其數據,使用會話ID對URL進行編程,以及顯示錯誤信息。這些標簽封裝在文件名為struts-html.tld的標簽包中。
l???????
Templete
標簽:
提供一組用于定義可重用的模板視圖的標簽,包括:<template:get/>、<template:insert/>、<template:put>。這些標簽被封裝在文件名為struts-template.tld的標簽包中。
Struts1.1
還引入了struts-nested和struts-tiles兩個標簽庫。在LPMT中,我們采用了Struts1.0的四個標簽庫。
6.2 WebForm的Jakarta Struts標簽技術實現
WebForm 即是通常 MVC 設計模式中的 View 。在 LPMT 中, View 由通過 HTML 和 Struts 標簽構建的 JSP 頁面充當。 JSP 頁面作為 View 使用,只負責顯示業務邏輯的處理結果,并不負責業務邏輯處理。為使 View 具有較高的可復用性,通常將多個邏輯顯示集合在一個 View 完成;為使 View 具有較高的穩定性, JSP 頁面應該保留通用的接口,即使 Action 的業務邏輯改變了, View 仍然不需要修改。另外,為使 View 具有較高的可擴展性和可維護性,降低未來可能的維護成本, View 結構應該清晰易讀。以 IssueDetail.jsp 為例說明 WebForm 的 Jakarta Struts 技術實現。
IssueDetail.jsp 的設計需求是要實現 Issue 創建、查看、修改、刪除的頁面顯示。當操作類型為創建 — “ create ”時, View 應該初始化一張空白的 Issue 表單,等待用戶輸入;當操作類型為查看 — “ viewDetail ”時, View 應該把相應的 Issue 的值及其 IssueData 、 ChangeLog 顯示出來,內容為不可編輯;當操作類型為修改時, View 表單應該初始化相應 Issue 的值并于相應的 HTML 控件集合,內容為可編輯;當操作類型為刪除 — “ delete ”時, View 不顯示或者顯示 Issue 的內容,不可編輯。 為實現上述上述邏輯,我們采用 <logic:equal> 標簽,通過判斷 issueActionForm 對象的 actionType 參數的 value 值來判斷操作類型,繼而實現相應的顯示邏輯。以顯示 Issue 的 description 值為例,代碼實現如下:
<logic:equal name="issueActionForm" property="actionType" value="create">
????????????? <html:textarea property="description" rows="8" cols="40"/>
?????? </logic:equal>
??? <logic:equal name="issueActionForm" property="actionType" value="edit">
????????????? <logic:present name="issueActionForm">
???????????????????? <html:textarea property="description" rows="8" cols="40"/>
????????????? </logic:present>
?????? ?????? <logic:notPresent name="issueActionForm">
????????????? ?????? <b>Error:No issue.</b>
?????? ?????? </logic:notPresent>
?????? </logic:equal>
?????? <logic:equal name="issueActionForm" property="actionType" value="viewDetail">
????????????? <logic:present name="issueActionForm">
???????????????????? <bean:write name="issueActionForm" property="description" />
????????????? </logic:present>
????????????? <logic:notPresent name="issueActionForm">
???????????????????? <b>Error:No issue.</b>
????????????? </logic:notPresent>
?????? </logic:equal>
?????? 而在 View 通用性設計方面, IssueDetail.jsp 通過使用 <logic:present> 和 <logic:notPresent> 標簽判斷對象 issueActionForm 是否已經加載到 scope 以及加載進來的 issueActionForm 的 actionType 等屬性值來完成邏輯顯示。這意味著當 Action 完成相應的業務邏輯之后,在返回指向 IssueDetail.jsp 的 ActionMapping 對象之前,只需要把相應的 IssueActionForm 對象實例加載到如 Request 、 Session 或者 PageContext 即任何一個 scope 里面就可以了,不需要在 URL 中傳遞任何參數。
比如在 IssueAction 中,當操作類型為查看相應 Issue 的詳細內容時, URL 請求為 http://localhost:8000/issuecontrol/issueAction.do?action=viewDetail&issueId=... , IssueAction 先取得 URL 傳遞過來的 issueId 參數值,然后通過 IssueBean.getIssueById 方法取得對應的 IssueActionForm 對象實例 issueActionForm ,設置 issueActionForm 對象的 actionType 參數,把 issueActionForm 放到 httpServletRequest 對象中,最后設置轉向參數 address 的值為 ”viewIssueDetail” 。代碼示例如下:
if("viewDetail".equals(action)) {
String issueId = "";
? ????????? if(httpServletRequest.getParameter("issueId")!=null)
issueId = httpServletRequest.getParameter("issueId").trim();
IssueActionForm issueActionForm = IssueBean.getIssueById(issueId);
if(issueActionForm == null) {
runningErrors.add(new ActionError("Issue(IssueID=" + issueId + ") not found."));
??????? ???? httpServletRequest.setAttribute("runningErrors",runningErrors);
??????? ???? address = "error";
????? ? }
????? ? else {
??????? ???? httpServletRequest.setAttribute("issueActionForm",issueActionForm);
?????? ????????????? issueActionForm.setActionType(action);
??????? address = "viewIssueDetail";
??? ????? }
}
當操作類型為新建一個 Issue 實例的時候, URL 請求為 http://localhost:8000/issuecontrol/issueAction.do?action=create , IssueAction 取得 typeActionForm 對象數組、 componentActionForm 對象數組、 flagActionForm 對象數組、 environmentActionForm 對象數組和 priorityActionForm 對象數組并放到 httpServletRequest 對象中,最后設置轉向參數 address 的值為 ”viewIssueDetail” 。代碼示例如下:
?????? else if("create".equals(action)) {
??? ????? httpServletRequest.setAttribute("typeActionForms",IssueBean.getAllTypes());
httpServletRequest.setAttribute("componentActionForms",IssueBean.getAllComponents());
httpServletRequest.setAttribute("flagActionForms",IssueBean.getAllFlags());??? httpServletRequest.setAttribute("environmentActionForms",IssueBean.getAllEnvironments());
????? ? httpServletRequest.setAttribute("priorityActionForms",IssueBean.getAllPriorities());
????????????? address = "viewIssueDetail";
}
當操作類型為編輯修改一個 Issue 實例的時候, URL 請求為 http://localhost:8000/issuecontrol/issueAction.do?action=edit&issueId =... , IssueAction 根據取得的參數 issueId 的值取得相應的 IssueActionForm 對象實例 issueActionForm ,設置 issueActionForm 的參數 actionType 的值,取得 typeActionForm 對象數組、 componentActionForm 對象數組、 flagActionForm 對象數組、 environmentActionForm 對象數組和 priorityActionForm 對象數組并將所有對象實例放到 httpServletRequest 對象中,最后設置轉向參數 address 的值為 ”viewIssueDetail” 。代碼示例如下:
else if("edit".equals(action)) {
address = "viewIssueDetail";
String issueId = "";
if(httpServletRequest.getParameter("issueId") != null)
IssueId = httpServletRequest.getParameter("issueId").trim();
IssueActionForm issueActionForm = IssueBean.getIssueById(issueId);
if(issueActionForm == null) {
runningErrors.add(new ActionError("Issue(IssueID=" + issueId + ") not found."));
??? ????? httpServletRequest.setAttribute("runningErrors",runningErrors);
??? ????? address = "error" ;
}
else {
issueActionForm.setActionType(action);
??? ????? httpServletRequest.setAttribute("issueActionForm",issueActionForm);
httpServletRequest.setAttribute("typeActionForms",IssueBean.getAllTypes());??????? httpServletRequest.setAttribute("componentActionForms",IssueBean.getAllComponents());
httpServletRequest.setAttribute("flagActionForms",IssueBean.getAllFlags());??????? httpServletRequest.setAttribute("environmentActionForms",IssueBean.getAllEnvironments());??????? httpServletRequest.setAttribute("priorityActionForms",IssueBean.getAllPriorities());
address = "viewIssueDetail";
? ???? }
}
另外,由于 View 只負責邏輯顯示,而不需要實現業務邏輯,因此結構非常清晰,擴展和修改都將非常容易,可以將擴展和修改維護的成本降低到很小的水平。
6.3 與單純的JSP代碼的比較
與目前業界比較常用的單純 ASP/JSP 代碼構建的 WebForm 方法相比,上述 WebForm 構建技術有明顯的優勢:
l??????? 構建成本低,效率高
Jakarta Struts 標簽庫提供了一系列的標簽用于快速構建 JSP View ,標簽庫包括一般的 HTML 元素和控件 (html 標簽 ) ,流程和邏輯控制的標簽 (logic 標簽 ) , bean 操作標簽等等。這里以循環顯示所有的 Component 為例比較一下兩種方法的效率,假設 Component 對象數組可以從 request 中得到。
Logical 標簽:
<logic:iterate id="components" name="componentActionForms">
?????? ?????? <bean:write name="components" property="component" />
</logic:iterate>
單純的 JSP 代碼:
<%
?????? ComponentActionForm[] components
= (ComponentActionForm[])request.getAttribute(“componentActionForms”);
????????????? For(int i=0;i<components.length;i++) {
???????????????????? out.println(components[i].getComponent);
}
%>
可以預見,顯示邏輯越復雜,用 Jakarta Struts 標簽庫構建的 JSP View 效率越高,結構也更清晰,構建和維護成本更低。詳細代碼請看附錄 IssueDetail.jsp 代碼。
l??????? 可擴展性高
對于單純的 JSP/ASP 代碼構建的 View 而言,由于業務邏輯通常是在 JSP 頁面完成的,所以當業務邏輯改變或擴展之后,相應的 View 也必須改變;如果業務邏輯是在多個頁面完成的話,修改的成本更大,可擴展性更差。而用 Jakarta Struts 標簽庫構建的 View ,由于業務邏輯在相應的 Action 中完成,所以只需要修改相應的 Action 操作就可以了。尤其當 Action 復用程度比較高的時候,這種修改需要的工作量更小,而且 View 根本不需要修改,這種優勢也是 MVC 設計模式 Model 和 View 的分離所帶來的良好的可擴展性。
l??????? 可重用性高
運用 Jakarta Struts 的 Logical 標簽,可以構建高度可復用的 WebForm 。雖然單純的 JSP/ASP 代碼技術也可以實現,但由此所帶來的構建、修改和維護成本將隨著邏輯的復雜度增加而急劇增長。比如上文提到的,運用 Jakarta Struts 的 Logical 標簽,我們很容易將 Issue 的創建、查看、修改、刪除顯示邏輯集中在一個 IssueDetail.jsp View 中完成。
l??????? 可讀性高,結構清晰
Jakarta Struts 標簽和 HTML 代碼很接近,不需要額外的用 <%%> 等標簽包含起來,可讀性更高。而 Jakarta Struts 標簽集合了邏輯和流程控制,與 HTML 結合所構建的 JSP View 結構更清晰。下圖為 HTML 中內嵌 JSP 代碼和 Struts 標簽的代碼截圖,可以看到,使用 Struts 標簽的 View 結構更清晰,更易讀。
圖 14 HTML 中內嵌 JSP 代碼
圖15 HTML中內嵌Struts標簽代碼