简介 大家期待已久的JavaServer FacesTM(JSF)版本1.0和JavaServer PagesTM(JSP)版本2.0终于发布,它们承诺要改变J2EE开发人员构建Web应用程序的方式。与此同时,可扩展样式表语言转换(Extensible Stylesheet Language Transformation,XSLT)版本2.0已经处于规范制定的最后阶段,而许多开发人员正在认真地考虑基于XML的表示层,尤其是当应用程序以多种设备为目标,或者要求外观定制时。 本文说明了一点,即JSF 1.0和JSP 2.0是逻辑演变的结果,而这种演变早在servlet编程的早期就开始了。借助诸如JSP 1.1、Struts 1.0和JSLT 1.0这些里程碑式的技术,Java技术已经逐渐集成了来自实际开发领域的多种成功技术。接着,本文介绍了Model 2X,它结合了JSP技术和XML处理,是上述演变的延续,并且增强了表示层的灵活性。 为了更好地理解本文中讨论的概念,您应该具有JSP、XML和Web应用程序架构方面的基本知识。与JSF概念的某些相似性将会大有帮助。 注意:书写本文过程中所使用的JSF规范的版本是JavaServer Faces 1.0 Specification Early Access Draft,版本日期为2002年9月6日。该规范将在未来不断完善。所使用的JSF实现是Sun Reference implementation v1.0,Early Access 2(EA2)。 本文涉及到的所有源代码都可以从下面的网址下载得到: 对于Java开发人员来说,下面列举的都是激动人心的时刻。在撰写本文的时候(2003年2月):
即便您能够做到与时俱进,规范和首字母缩写词的丰富程度还是令人望而却步。好消息是,所有这些技术都彼此相关,且设计为彼此互补。
下图说明了各种技术之间的相关性: 图1:技术堆栈 从前… 在开始实现JSP之前,服务器端的Java应用程序通过在Java代码中嵌入字符串来生成HTML。例1说明了从servlet生成简单的HTML表的过程。 代码如下: MyTableData tableData = MyDAO.queryData(); PrintWriter writer = response.getWriter(); writer.println("<table border=\"1\">"); for (int i = 0; i < tableData.getData().length; i++) { writer.println("<tr>"); writer.println("<td>"); writer.println(tableData.getData()[i]); writer.println("</td>"); writer.println("</tr>"); } writer.println("</table>"); 生成的结果如下所示:
例1:一个从Servlet生成的HTML表 这个模型存在以下问题:
许多页面包含大量的静态的、非生成的文本(需要是可编辑的),以及相对较少的动态生成的内容。为此,在现实世界中,针对这个问题的解决方案大都基于页面模板的机制。模板系统使得固定文本和动态动作的交叉变得更加自然。在Java世界中,当前使用的最受欢迎的模板是JSP。 JSP的早期 借助JSP,HTML和Java代码可以同时出现在一个JSP文件中。JSP文件也称为JSP页面。JSP引擎动态装载并编译JSP页面。这使得开发流程变得更加简易。另外,不要求Java代码生成静态的模板文本。只有动态的动作才要求使用Java。 例1可以被重写为下面的JSP 1.1代码片断: <% MyTableData tableData = MyDAO.queryData(); %> <table border="1"> <% for (int i = 0; i < tableData.getData().length; i++) { %> <tr> <td> <%= tableData.getData()[i] %> </td> </tr> <% }%> </table> 例2:使用JSP显示一个表 HTML代码被书写为模板文本。Java代码被封装在称为scirptlet的结构体中。语言的混合晦涩难懂,大部分非Java开发人员对此都力不能及。这使得在开发过程中划分任务和角色变得困难。 在例2中,业务逻辑是直接从JSP页面被调用的。通常,开发人员在JSP页面中实现一些或甚至全部的业务逻辑。这并没有指定页面与业务逻辑的交互,因此强制程度降低了很多。对于大型的项目来说,除非开发人员的技术素养非同一般,否则这种行为将导致代码变得非模块化,而且难于维护。 例2说明了以下问题必须得到解决:
JSP 1.1规范于1999年11月定稿。自从JSP 1.1以来,JSP最佳实践已经取得了显著的进展,虽然它们当时实际上还不存在,但是这种样式的JSP页面仍然普及开来。要解决JSP 1.1的问题,必须在JSP技术上构建框架。 Struts和MVC Struts是实现了解决这些问题的架构而且基于JSP的第一批框架之一:
在Struts中,MVC架构是以页面为基础工作的。它是通过叫做Model 2的混合架构来实现的,涉及到servlet与JSP文件的协作。 下图说明了在Struts中,MVC的不同部分(模型、视图和控制器)之间的关系。Struts提供了控制器servlet。模型是在一个实现或调用业务逻辑的动作类中实现的,而视图是在一个JSP页面中实现的。您仍然能够在JSP页面中实现业务逻辑,但实际上不鼓励这种做法。 图2:Struts、MVC和Model 2 MVC架构相当重要,因为它将使代码自然而然地模块化,不论是开发小组还是单个开发人员,都可以从中获益匪浅。当几个开发人员一起工作时,较容易划分角色:一个开发人员专门处理业务逻辑,同时其他人负责构建实际的Web页面。想要了解有关MVC的更多信息,请参见本文结尾的参考资料。 例3显示了使用Struts重写例2中JSP代码片断的结果: <table border="1"> <logic:iterate id="data" name="tableData" property="data"> <tr> <td> <bean:write name="data"/> </td> </tr> </logic:iterate> </table> 例3:使用 Struts显示一个表 现在,调用业务逻辑的代码实现在一个单独的动作类(没有给出)中,这个动作类传递一个名为tableData的JavaBean给JSP页面。例4显示了上述bean的样子: public class TableData { private String[] data; public String[] getData() { return data; } ... } 例4:简单的 JavaBean logic:iterate标签的name和property属性指定了bean的data属性元素上的一次迭代。property属性还支持嵌套的和索引的引用,它们构成了JSP 2.0中使用的表达式语言(Expression Language,EL)和JSTL的实际先驱。例3说明,通过实现MVC并提供足够的标签库,您可以在JSP文件中完全不使用Java代码。 步入JSTL和JSP 2.0 Struts标签库和表达式语言存在限制。考虑下面例子中的代码,它的功能是使用另外一列和交替的行颜色构造一个HTML表: 代码如下: <% MyTableData tableData = MyDAO.queryData(); %> <table border="1"> <% for (int i = 0; i < tableData.size(); i++) { String cellColor = (i % 2 == 0) ? "gray" : "white"; %> <tr> <td bgcolor="<%= cellColor %>"> <%= i %> </td> <td bgcolor="<%= cellColor %>"> <%= tableData.get(i) %> </td> </tr> <% }%> </table> 生成了如下所示的结果:
例5:使用Scriptlet的复杂例子 在例5中,不能使用Struts标签替换所有的scriptlet,因为迭代包括一个数据元素、一个位置索引和一次Struts标签库不能处理的计算。 针对这些限制的解决方案是引入JSTL和JSP 2.0。JSLT实质上是Struts标签库的一个经过深思熟虑的更好的版本。标签的集合更加一致而完备。JSTL引入了表达式语言(EL),这种语言松散地基于JavaScript和XPath。JSP 2.0天生就支持这种EL。 除了对EL的天生支持,JSP 2.0还具有以下新特性:
另外还有一些与使用JSTL和EL相关的优点:
例5可以使用JSTL标签库和JSP 2.0全部重写,如例6所示: <table border="1"> <c:forEach items="${tableData.data}" var="currentData" varStatus="status"> <c:set var="cellColor"> <c:choose> <c:when test="${status.index % 2 == 0}">gray</c:when> <c:otherwise>white</c:otherwise> </c:choose> </c:set> <tr> <td bgcolor="${cellColor}"> ${status.index} </td> <td bgcolor="${cellColor}"> ${currentData} </td> </tr> </c:forEach> </table> 例6:使用 JSTL和 JSP 2.0书写的例子 最新的规范明确支持书写脚本更少的页面,这标志着JSP开发的一个转折点。从纯化论者认为的HTML"tag soup"与Java scriptlet的无形混合,到几乎纯粹的命名空间感知的XML文件,JSP页面在最佳实践方面发生了重大变化。结果是语法更加一致,页面作者的工作负担减轻,与工具的集成更加容易,以及可以更好地控制业务逻辑与表示逻辑的分离。 JSP的前景 对于与用户的交互有限而且简单的动态Web页面来说,JSP 2.0、JSTL和MVC就能提供您所需要的全部功能。但是如果要开发复杂的、基于Web的用户界面,您不得不做大量的工作,以便提取请求参数、验证并处理它们、将它们呈现回为HTML控件,等等。整个过程冗长乏味,而且极易出现错误。 长期以来,桌面应用程序已经使用了基于组件的层次MVC框架,比如降低构建用户界面的难度的Swing。此类框架将用户界面的各个元素公开为以Container/Containee关系组织的组件。这种方法为自身提供了使用图形工具构建用户界面的能力。它还允许组件重用,以及在开发过程中进行更好的角色分离。 图3:包含控件的窗体 通常,Web应用程序具有简化的控件集合和有限的交互性,但在其他方面与桌面应用程序区别不大。我们很自然地想到,要试着将在桌面上使用的模型应用程序到服务器端的世界中去。这也是JSF专家组选出的解决方案。服务器端的应用程序与桌面应用程序相去甚远,所以JSF需要一个新的处理模型及API,从而代替Swing中模型和API的使用。 像Struts一样,JSF符合MVC架构。它提供一个控制器servlet,并且允许使用呈现程序的视图和使用组件类及基于事件的机制的模型分离开来。借助JSF,MVC可以工作在单独组件的层次上。 开发人员能够在JSP文件中使用XML标记来描述组件之间的相互关系,而不用书写Java代码来组装JSF组件,而后者正是Swing的做法。联合使用JSF与JSP是可选的,但这是众望所归的首选技术。例如,JSF附带有一组标准的用户界面组件,可以在JSP中通过Standard JSF Tag Library来使用它们。这允许不擅长Java的web页面作者也能从事开发复杂用户界面的任务。它还使得实现图形工具更加容易。本文稍后将讨论这样的一个例子。 当与JSP一起使用时,因为同时使用了控制器servlet和JSP页面,JSF可以被看作是一个Model 2架构。 JSF的承诺包括重用、角色分离和易于使用的工具,从而降低了实现服务器端用户界面的门槛,并缩短了其所需的时间。 本文的余下部分专门讲述JSF特殊的呈现方面。想要了解JSF架构的更多信息,包括它的API和处理模型,请参见本文结尾部分的参考资料。 JSF呈现 用户界面的呈现在于生成信息以使其对用户可见。在服务器端编程领域中,信息通常包括HTML、CSS和JavaScript。呈现涉及到发送字符流给Web浏览器。 呈现JSF层次是一个递归过程,在此过程中,每个使用Java书写的组件都有机会呈现自身。JSF提供了两个选择:
这两个选择都不是基于模板的,而且就像在刚开始使用servlet的时候,它们还要求非标准的解决方案或者从Java代码生成HTML。早期访问版本的JSF附带的呈现程序便是以这种方式实现的。无论何时呈现需要改变,组件开发人员都不得不卷入到其中。与JSP日益提倡的角色分离相比,这是一种严重的,而且几乎是不能理解的倒退。 HTML有一种伴生语言,称为CSS,特别为样式而设计。假如在JSF的HTML RrnderKit for the Standard User Interface Component中,能够使用CSS支持,那么CSS便能够解除JSF呈现的限制。例如,可以使用支持CSS的浏览器中的style属性来选择按钮的背景颜色: <input type="button" value="Button 1" style="color: red"/> <input type="button" value="Button 2" style="color: blue"/> 例7:使用 CSS选择样式 通过允许保持组件的呈现代码不变,即便是在另外的呈现方面改变时也是如此,CSS的使用部分地解决了样式问题。这种方法具有以下限制:
为了说明第一点,考虑一下,您可能想要应用程序中的每个表都交替显示行的颜色,就像例5和6那样。CSS不支持描述这种行为。 对于不支持最新版本CSS的Web浏览器来说,一个流行的不使用CSS的HTML技巧便是通过嵌入表来创建表边界。 下面的代码: <table cellpadding="0" cellspacing="0" border="0" bgcolor="red"> <tr> <td> <!-- Original table --> <table width="100%"> <tr> <td bgcolor="white">1.1</td> <td bgcolor="white">1.2</td> </tr> <tr> <td bgcolor="white">2.1</td> <td bgcolor="white">2.2</td> </tr> </table> </td> </tr> </table> 生成的结果如下所示:
例8:嵌入表 要处理这种使用JSTL的情况,代码不得不在应用程序的每个JSP页面中被重复。如果在开发过程中,开发人员不得不在不同的样式之间来回切换好几次,例如要处理变化的需求时,修改必须在整个应用程序中重复进行。借助JSF,可以构建一个定制的组件,但是这意味着,您必须书写复杂的Java代码。如果要实现这种类型的样式技术,两种技术都帮不上什么忙。 至少有两种解决方案是可用的:
Model 2X 架构 Model 2X最重要的方面是XML转换语言的使用,比如XSL转换(XSL Transformation,XSLT),连同servlet和JSP。下图说明了Model 2X的基本架构: 图4:Model 2X架构 servlet或JSP的输出通常被直接发送给Web浏览器。有了Model 2X之后,在到达Web浏览器之前,输出先要经由一个XML转换的阶段。为了理解这一点,考虑JSP和XML两个世界之间的关系相当重要。 JSP和XML 设计JSP的目的是为了能够产生任意的文本格式,比如HTML、逗号分隔值(Comma-Separated Value,CSV)以及层叠样式表(Cascading Style Sheet,CSS)。出于这个理由,您可以使用JSP生成XML文档。要注意的地方是,JSP不能强制生成格式良好的XML输出。为了生成格式良好的XML,您不得不采取以下预防措施:
考虑下面这个例子,即包含有效HTML标签的JSP代码片断: <body> <hr> </body> 有一种方法可以确保模板文本是格式良好的XML,即书写Sun所号称的JSP文档,而不是常规的JSP页面。JSP文档不过就是JSP页面的XML版本而已。JSP结构体,比如 标签库经常生成HTML。Struts的html标签库和标准JSF标签库便是如此。使用这种标签库时,必须修改它们,以便生成XML,而不是HTML。借助JSF可以轻松完成这项工作,具体方法是书写一个XML呈现程序,并用它来代替现有的HTML呈现程序。本文稍后将详细讨论这个问题。 执行JSP页面的结果仍然会得到字符流,即便该页面符合XML规范也是如此。不能确保输出是有效的,或者甚至是格式良好的XML。如果工具需要将JSP输出处理为XML,如Model 2X中所示,这时便需要一个解析的步骤。 XSLT XML被JSP页面生成之后,它就可以被其他工具消费。XSLT便是一个这样的工具,一种被设计用来执行XML文档转换的功能语言。 一个简单的XSLT转换可以通过下面这种方式来表示:
图5:简单的XSLT转换 XSLT转换的结果可能是XML、HTML或者其他任意的文本格式。 XSLT的1.0版本发布于1999年11月。从那之后,为了将它发展为XSLT 2.0规范,许多人付出了大量的劳动。XSLT 2.0基于XPath 2.0,是一种新版本的XML表达式语言,具有更好的一致性,而且包含1.0版本中所没有的一些特性,比如分组和用户定义的XPath函数。 一个XSLT程序被称为一个样式表。这个术语在XSLT 2.0规范草案中有着清晰的表述: "术语样式表反映了这样一个事实,即XSLT的重要角色之一便是为XML源文档添加样式信息,具体方法是将它转换为由XSL格式化对象(参见[XSL格式化对象])组成的文档,或者转换为另一种面向表示的格式,比如HTML、XHTML或SVG。" 因此,XSLT这种语言的设计目的相当明确,就是为了解决上述的样式问题。 JSF和Model 2X 现在,您已经能够理解在Model 2X中,XML和XSLT技术是如何与JSP和JSF进行交互的。下面的JSP代码片断使用了JSF,这段代码来自当前JSF引用实现中的"guessNumber"例子。它包含了HTML模板代码和构建简单窗体的JSF标签: <%@ taglib uri="http://java.sun.com/j2ee/html_basic/" prefix="faces"%> <html> <head><title>Hello</title></head> <body> <h1>Hello</h1> <faces:usefaces> <faces:form id="helloForm" formName="helloForm"> <table> <tr> <td> <faces:textentry_input id="userNo" modelReference="UserNumberBean.userNumber"/> </td> <td> <faces:command_button id="submit" commandName="submit"/><p> </td> </tr> </table> <faces:validation_message componentId="userNo"/> </faces:form> </faces:usefaces> </body> </html> 例9:JSF 例子 执行使用XML呈现程序的代码片断,可能会输出以下格式良好的XML文档: <html> <head><title>Hello</title></head> <body> <h1>Hello</h1> <form method="post" action="/guessNumber/faces/greeting.jsp"> <table> <tr> <td> <input type="text" name="/helloForm/userNo"/> </td> <td> <input type="submit" name="submit" value="submit"/> </td> </tr> </table> </form> </body> </html> 例10:JSF例子的输出 当JSP输出被解析并被反馈到XSLT转换器时,您可以轻松地应用程序样式。下图说明了处理模型,同时也是Model 2X的一个特殊例子: 图6:JSF和XSLT呈现模型 例如,要将应用程序中的每个按钮呈现为红色,使用下面的XSLT模板即可达到目的: <!-- Find all input elements with a "submit" type attribute --> <xsl:template match="input[@type = submit]"> <!-- Copy the element found --> <input> <!-- Copy all existing attributes --> <xsl:copy-of select="@*"/> <!-- Add a "style" attribute --> <xsl:attribute name="style">color: red</xsl:attribute> </input> </xsl:template> 例10:使用一个XSLT 模板添加样式 XSLT模板并不限于在由JSF呈现程序生成的标记上使用。例如,它可以使用同样的技术生成页面布局。例11说明了如何插入一个表,该表包含一个标题行以及包含原始文档主体的第二行: <!-- Match the body element --> <xsl:template match="body"> <!-- Copy the element found and set a background color --> <body bgcolor="white"> <table border="0"> <!-- Create a header row --> <tr> <td>Header</td> </tr> <tr> <td> <!-- Copy the actual body --> <xsl:apply-templates/> </td> </tr> </table> </body> </xsl:template> 例11:创建一个页面布局 Model 2X的优点包括:
Model 2X的以下限制应该引起注意:
XML 管道 Model 2X的使用为JSP处理模型引入了一个新的维度,因为它涉及到几个顺序执行的步骤:
实际上构建的是一个两段式管道。可以为每个阶段分配定义良好的角色: 管道的概念使Web应用程序的表示层变得模块化。不用修改JSP页面,管道就可以被扩展为包含具有不同角色的另外阶段,例如:
管道的一个完整例子如图7所示: 图7:管道的例子 实现Model 2X 使用JSF实现Model 2X要求:
下面的代码说明了对第一步的需要。JSF faces:textentry_input标签不能生成格式良好的XML输出: <INPUT TYPE="text" NAME="name" VALUE="value"> 生成XML的JSF RenderKit必须生成格式良好的XML元素和属性。另外,它应该遵照以小写形式创建元素的XHTML惯例: <input type="text" name="name" value="value"/> JSF的当前引用实现没有附带一个XML RenderKit,但是对于熟悉JSF的开发人员来说,书写这样一个呈现程序不是什么难事。本文中包含了一个简单的RenderKit。 使用以下方法可以完成与XML解析器和XSLT转换器的连接:
想要了解更多细节,请参见本文中使用的示例源代码。 结束语 这些年来,JSP技术已经趋于成熟。最佳实践已经表现为在真实应用程序中使用JSP的结果,这导致JSP技术的核心得到了很大的增强,并使诸如JSTL这样的新规范得到发展。JSF引入了一个期待已久的用户界面组件模型,但是JSF也表现出其在呈现架构中受到限制的地方。众多开发人员可能认为这些限制是一个概念上的倒退。 Java开发人员相当熟悉XML技术,但他们没有必要了解XML是如何被用于增强Web应用程序的表示层的。Model 2X是一个简单的架构,它构建在JSP、JSF和XML技术的基础之上,减少了JSF呈现模型的限制,并为J2EE应用程序提供了一个灵活的表示层。 尽管JSP有逐渐与XML同化的趋势,JSP 2.0还是没有与XML标准完全集成。将来,通过将Model 2X添加到JSP引擎的核心中,JSP应该完成这种演变。 |