编译:彭帅
组织HTML和JSP视图组件的一些解决方案
概述:
许多Web应用程序开发者必须自己组合视图组件,例如header, body和footer. 如今已存在很多组合这些组件的技术,每一种都有它自身的优缺点.本文包含了七个可选的方案,并向你展示了Tiles和struts框架的灵活性.从一个简单的用来阐述组合视图组件问题的例子开始,然后使用JSP内建机制,如include,来干净地解决这个问题.继续下去,我们将通过利用tiles框架来展示其它的可选方案,再然后将学习如何去融合tiles和struts框架的各自优势.
典型地,在web应用程序开发过程中通常由用户界面小组来负责创建站点的外观.小组创建用来展现应用程序功能和导航的HTML页面.连同一个基于servlet和JSP的实现,HTML页面被转换于其中.UI开发者识别普通HTML和JSP视图组件,如header, footer, body, menu和 search.本文展示了许多种方案用以高效和便利地组合HTML和JSP视图组件.我用一个专门的标准(如页面数量,代码重复和布局控制)来评估每种方案.
为展示模板和布局方案,我们将使用tiles框架.Tiles框架的视图组件被认作是tiles.这个框架使用了一个XML配置文件来组合这些tiles.这个框架不仅仅让你能重用tiles,也可让你重用用以组织它们的布局.
为了展示更强大和灵活的方案,我们将研究tiles和struts框架间的结合.
评价标准
我将基于以下标准来评价各种方案.这些标准并非互斥的.在一个特殊的情况下和专门的应用程序,你必须权衡这些方案各自的优缺点.
页面数量
一个方案应该努力做到最小化HTML页面和JSP页面的数量.伴随着页面数量的增长,开发,管理和维护一个应用程序的复杂性也剧烈地增长.
代码重复
在多数环境下,重复是不好的.有越多重复的HTML和JSP代码,开发和维护一个应用程序的困难也越多.一个简单的改变可能导致在许多不同的页面中一系列级联的改变,并导致很多不可预知的结果.一个具体和实际的想达到重用的方式就是避免代码重复.
布局控制
代码重复是不好的,布局逻辑和代码的重复同样也是不好的.散布视图组件的逻辑和行为到许多JSP可能导致灾难性的后果.达到模板和布局逻辑重用是一个比仅仅只重用视图组件更好的重用形式.从而,你能获得具有可高效布局控制能力的更高级别的重用.
耦合
耦合代表实体之间互相作用的程度.软件工程师被重复告戒要做到最小化不相关类与包之间的耦合度.我们能够应用同样的原则到视图组件.即使从一个用户角度看来是截然不同的视图组件,在JSP实现中,组件可能杂乱地耦合了.我们的解决方案应该降低在不相关视图组件间的耦合度.
复杂性
复杂性带来增长的开发和维护开销,导致一个更复杂的不适当的解决方案.复杂度增长得很快,可能原先看起来简单和无关的东西在你添加了更多的部分进去之后很快就造成了巨大的混乱.
解决方案
我将使用一个基本的具有普通视图组件(如header和footer)的JSP例子来评估多种方案.我将会以依次增长复杂性的方式来展示这些方案,之后我将依据评价标准来仔细权衡每种方案.
方案一:基本JSP
看如下的a.jsp:
<html>
<body>
Header
<p>
a's body...
<p>
Footer
<p>
</body>
</html>
看如下的b.jsp:
<html>
<body>
Header
<p>
b's body...
<p>
Footer
<p>
</body>
</html>
在很多情况下,开发者从UI小组获得代码并且逐步把它转换到JSP页面中.如上,每个JSP有一个重复的header和footer.方案1是不合需要的,因为在普通视图组件中的改变,如header和footer,需要在所有相关的页面中都有相应改变,并且每个页面负责布置它们自己的视图组件.采用这个简单的方案是缺乏长远之见.伴随如此多HTML和JSP代码副本,我们虽最小化了页面数量但付出了沉重的维护代价.在不同视图组件中存在着很大的耦合,这些,如我前面所述,是不推荐的.
方案2:JSP include
如下a.jsp:
<html>
<body>
<%-- include header --%>
<jsp:include page="/header.jsp" />
a's body...
<p>
<%-- include footer --%>
<jsp:include page="/footer.jsp" />
</body>
</html>
如下b.jsp:
<html>
<body>
<%-- include header --%>
<jsp:include page="/header.jsp" />
b's body...
<p>
<%-- include footer --%>
<jsp:include page="/footer.jsp" />
</body>
</html>
注意,普通视图组件,如header和footer,通过使用JSP include机制被分离.
header.jsp
:
Header
<p>
footer.jsp
:
Footer
<p>
方案2巧妙地避免了在方案一中的一些主要缺点.你仅仅需要改变普通视图组件一次.因此,这个方案很大程度地消除了HTML和JSP代码重复,显著地改进了应用程序的可维护性.它增加了页面数量,但是急剧地降低了普通视图组件和其它页面之间的紧耦合.在复杂性方面,这个解决方案在真实应用程序中是简单并且容易实现的.但是,它有一个主要的障碍:假如你改变了如何或在哪里组合视图组件(i.e.,改变组件布局),你将要更新每个页面――导致了一个规模巨大的变化.方案2达到了视图组件重用,但是没有获得布局和模板逻辑重用.
方案3:tiles insert
a. jsp:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<html>
<body>
<%-- include header --%>
<tiles:insert page="/header.jsp" flush="true"/>
a's body...
<p>
<%-- include footer --%>
<tiles:insert page="/footer.jsp" flush="true"/>
</body>
</html>
b.jsp:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<html>
<body>
<%-- include header --%>
<tiles:insert page="/header.jsp" flush="true"/>
b's body...
<p>
<%-- include footer --%>
<tiles:insert page="/footer.jsp" flush="true"/>
</body>
</html>
代替使用JSP include机制,方案3使用tiles insert机制.使用tiles insert标签,你可以包含视图组件在适当的位置.从所有其它方面来看,这个方案镜像了JSP include方案,具有与之相同的优缺点.
方案4:分割body
a. jsp
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<html>
<body>
<%-- include header --%>
<tiles:insert page="/header.jsp" flush="true"/>
<%-- include body --%>
<tiles:insert page="aBody.jsp" flush="true"/>
<%-- include footer --%>
<tiles:insert page="/footer.jsp" flush="true"/>
</body>
</html>
b.jsp:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<html>
<body>
<%-- include header --%>
<tiles:insert page="/header.jsp" flush="true"/>
<%-- include body --%>
<tiles:insert page="bBody.jsp" flush="true"/>
<%-- include footer --%>
<tiles:insert page="/footer.jsp" flush="true"/>
</body>
</html>
方案4与tiles insert方案有稍微的不同之处.方案4把body分割成了一些单独的页面,如aBody.jsp
和bBody.jsp
.
aBody.jsp:
a's body...
<p>
bBody.jsp
:
b's body...
<p>
方案4的优点:它把body的变化分解到各个独立的页面中.同样地,它让你可以在其它地方重用body,消除了重复.因此,这个解决方案更进一步地减少了普通视图组件和其它应用程序组件之间的耦合.创建和管理每个body组件引入了一个额外的复杂性级别.和其它各个方案一样,每个页面仍然是由自己来控制自身的布局.因此,没有集中式的布局策略和配置.
方案5:templating tiles
使用tiles的模板特性,你可以定义如下布局(如下layout.jsp文件展示)作为一个模板.因为这是一个布局,所以你应使用tiles insert标签插入占位符来代替实际的视图组件.因此,对所有的组件来说,这个页面定义了一个可重用的布局:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<html>
<body>
<%-- include header --%>
<tiles:insert attribute="header"/>
<%-- include body --%>
<tiles:insert attribute="body"/>
<%-- include footer --%>
<tiles:insert attribute="footer"/>
</body>
</html>
其它内容页面,如a.jsp和b.jsp,使用以上的布局来排放组件.在实际的页面中,你通过使用Tiles insert标签来插入布局.使用tiles put标签,你可以为所有在布局中指定的占位符指定实际的视图组件.
a. jsp:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<tiles:insert page="/layout.jsp" flush="true">
<tiles:put name="header" value="/header.jsp"/>
<tiles:put name="body" value="/aBody.jsp"/>
<tiles:put name="footer" value="/footer.jsp"/>
</tiles:insert>
b. jsp:
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
<tiles:insert page="/layout.jsp" flush="true">
<tiles:put name="header" value="/header.jsp"/>
<tiles:put name="body" value="/bBody.jsp"/>
<tiles:put name="footer" value="/footer.jsp"/>
</tiles:insert>
方案5最典型的优点是它封装了布局模式和布局机制,急剧地降低了普通视图组件和其它内容之间的耦合.但是,它因引入另外的布局页面而增加了复杂性.首先,理解和实现模板同样也是一个困难.
方案6:Strut和tiles
下面的布局页面,layout.jsp,包含了用以组合组件的HTML和JSP代码.内容页面a.jsp和b.jsp,不包含任何HTML代码;它们仅仅包含tiles标签以插入必要的组件.我们何不在一个XML配置文件中指定所有的内容组件呢???
命名tileDefinitions.xml并且定义它的页面内容:
<?xml version="1.0" encoding="ISO-8859-1"?>
<component-definitions>
<definition name="aDef" path="/layout.jsp">
<put name="header" value="/header.jsp"/>
<put name="footer" value="/footer.jsp"/>
<put name="body" value="/aBody.jsp"/>
</definition>
<definition name="bDef" path="/layout.jsp">
<put name="header" value="/header.jsp"/>
<put name="footer" value="/footer.jsp"/>
<put name="body" value="/bBody.jsp"/>
</definition>
<definition name="cDef" path="/layout.jsp">
<put name="header" value="/header.jsp"/>
<put name="footer" value="/footer.jsp"/>
<put name="body" value="/cBody.jsp"/>
</definition>
</component-definitions>
通过把它们的定义放入XML文件的方式,方案6排除了所有的内容页面,如a.jsp和b.jsp.从此一个像a.jsp的资源再也不存在了,我们如何来请求它呢?更重要的是,我们如何请求tileDefinitions.xml文件中的定义的呢???
Struts和tiles之间强大和有效的集成达到了重用.除了正常的Struts配置参数,我们在web.xml文件中指定配置文件的位置为一个额外的参数.如下所示.指定definitions-config参数使struts能够找到并且知道tiles定义:
<!-- Standard Action Servlet Configuration (with debugging) -->
<servlet>
<servlet-name>action</servlet-name>
<!--
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
-->
<servlet-class>org.apache.struts.tiles.ActionComponentServlet</servlet-class>
<init-param>
<param-name>definitions-config</param-name>
<param-value>/WEB-INF/tileDefinitions.xml</param-value>
</init-param>
...
</servlet>
现在,我们定义一个struts action,它返回了一个在配置文件中的定义.Struts action DoFirst是一个非action,如下所示:
package com.malani.struts.action;
import org.apache.struts.action.*;
import javax.servlet.http.*;
public class DoFirst extends Action {
public ActionForward perform(
ActionMapping aMapping,
ActionForm aForm,
HttpServletRequest aRequest,
HttpServletResponse aResponse
) {
return aMapping.findForward("success");
}
}
你不能直接从浏览器调用一个定义,但是你可以从struts中调用一个其它的组件,使它看起来好像是一个实际的资源.如下所示,在struts-config.xml文件中定义Struts action:
<action path="/a"
type="com.malani.struts.action.DoFirst"
>
<forward name="success" path="aDef"/>
</action>
<action path="/b"
type="com.malani.struts.action.DoFirst"
>
<forward name="success" path="bDef"/>
</action>
<action path="/c"
type="com.malani.struts.action.DoFirst"
>
<forward name="success" path="cDef"/>
</action>
现在,分别请求a.do和b.do来调用Struts action将会返回想要的资源.
方案6最主要的优点是它固定了所有的定义在一个XML配置文件中.急剧减少了内容 页面的总数量.通过引入Struts,我们又给复杂性增加了另外的一个延伸方面.
方案7:tiles继承
在配置文件中,我们观察到每个页面定义看起来都是相似的.每个定义都有3个组件,其中的两个为header和footer.一个强大的tiles特性使定义之间能相互继承.因此,你可以定义一个基本的定义并让原先的定义继承这个定义.原先的定义必须只需要提供他们自己需要的部分.下面展示了在定义间使用了继承的XML配置文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<component-definitions>
<definition name="baseDef" path="/layout.jsp">
<put name="header" value="/header.jsp"/>
<put name="footer" value="/footer.jsp"/>
<put name="body" value=""/>
</definition>
<definition name="aDef" extends="baseDef">
<put name="body" value="/aBody.jsp"/>
</definition>
<definition name="bDef" extends="baseDef">
<put name="body" value="/bBody.jsp"/>
</definition>
<definition name="cDef" extends="baseDef">
<put name="body" value="/cBody.jsp"/>
</definition>
</component-definitions>
在配置文件中减少副本和多余信息是此方案的一个优点.总的来说,这个方案的优点和缺点和Struts和tiles方案的亦相似.
总结:
下面的表格以一些评价标准归纳了不同方案.你可以继续增加其它具有创造性的方案和其它重要的评价标准,如可扩展性,可维护性和性能.
以指定的标准来评估各种解决方案:
|
如表格所示,每个方案的复杂性级别逐渐增长.从中同样可以看到,随着复杂性的增长,代码重复随之减少,布局控制灵活性上身,并且不相关视图组件之间的耦合度下降.当各种视图组件分离时页面数量开始增长,但是当你在配置文件中定义更多的页面时,将还可以进行合并.
哪个方案是最佳的?
最佳的方案依赖于你的工程需要,需求和你开发和维护web应用程序的技能,知识.最基本的方案太过于简单;我不赞成它,因为它不符合软件工程实践的原则.假如你的web应用程序很复杂,模板将提供很好的布局控制.因此,你可能需研究和利用一个像tiles之类的模板框架.假如你已经在使用Struts,你将能配合使用Struts和Tiles来构建强大的解决方案.
非凡的设计
在这骗文章中,我评价了各种在HTML和JSP中组合视图组件的方案.我同样展示了如何让Struts和tiles框架协作.这些策略和方案将使你能够在你的web应用程序中基于纵多的可选方案来做出最佳的决策.