第五章 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标签代码