创建 JSP 2.0 标记文件
作者:Andrei Cioroianu
了解如何使用 JSP、JSTL 和 SQL 来创建可重用的 Web 模板和数据库脚本
下载本文的源代码
标记文件是 JavaServer Pages (JSP) 技术最重要的新增功能之一,它允许 Web 开发人员利用 JSP 语法创建自定义的标记库。JSP 容器自动将 JSP 标记文件转换为 Java 代码,其过程与从 JSP 页透明地生成 Java Servlet 的过程相同。可以说标记文件隐藏了创建自定义 JSP 标记库的复杂性。这种库能够在 Web 应用程序中重用,它们甚至在用于特定应用程序时也会提供显著的效益,因为自定义标记提高了 Web 页的可维护性。在阅读本文后,您将了解如何创建和使用标记文件以及如何将现有的页片段变换为标记文件。
JSP 2.0 定义了三个新的指示语句(<%@tag%>、<%@attribute%> 和 <%@variable%>)和两个新的标准操作(<jsp:invoke> 和 <jsp:doBody>),它们只能在标记文件中使用。我们将在整篇文章中使用它们,并且您将了解如何利用更加高级的标记文件特性,如“动态属性”、“片段属性”和“根据属性命名的变量”。当标记文件与 JSP 标准标记库 (JSTL) 同时使用时,标记文件可以是一种功能强大的工具,而 JSTL 引入了由 JSP 2.0 和许多 JSP 操作所采用的表达式语言,包括一系列 SQL 标记。我们将使用 JSTL 和高级的 JSP 特性来创建标记文件,用于更新和查询数据库。
标记文件概述
JSP 1.x 允许 Web 开发人员创建 Java 组件(称为标记处理程序),这些组件是通过自定义标记从 JSP 页进行调用的。标记处理程序类似于以前的 Java Servlet,因为您使用很多 println() 调用来生成 HTML 内容,然后必须编译您的 Java 代码。JSP 2.0 的标记文件类似于 JSP 页,因为您使用 JSP 语法,然后 JSP 容器获取您的 JSP 标记文件,分析这些标记文件,生成 Java 标记处理程序,并自动编译它们。标记文件是从 JSP 页进行调用的,JSP 页使用与 <prefix:tagFileName> 模式相匹配的自定义标记。
为了使标记文件能够被 JSP 容器所识别,标记文件必须使用 .tag 文件扩展名进行命名,并且必须被放置在您的 Web 应用程序的 /WEB-INF/tags 目录中或者 /WEB-INF/tags 的子目录中。如果您采用这种部署方法,则不必创建任何标记库描述器 (TLD),因为 JSP 库是利用 Java 标记处理程序所实施的。您也可以将标记文件放置在 .jar 文件的 /META-INF/tags 目录中,其部署更容易,但在这种情况下,您必须创建 TLD,并且必须在每次更改后重新对标记文件进行打包。
标记文件和 JSP 页使用几乎相同的语法。您会注意到的第一个区别是新的 <%@tag%> 指示语句,它等同于 <%@page%>。这两个指示语句具有相似的属性,但前者用于标记文件,而后者只能用于 JSP 页中。标记文件并不在一个单独的 .tld 文件中声明其属性和变量,而是使用 <%@attribute%> 和 <%@variable%> 指示语句。当从 JSP 页调用标记文件时,自定义标记可以具有主体(在 <prefix:tagFileName> 与 </prefix:tagFileName> 之间),该主体可以由标记文件利用 <jsp:doBody> 操作来执行。您在下一节中将会看到这是如何工作的。
使用标记文件代替页片段
为了拥有可维护的 Web 页,许多经过良好设计的 JSP 1.x 应用程序使用了页片段,这些页片段是利用 <jsp:include> 或 <%@include%> 而包含在较大页面中的,而这不是在 Web 应用程序中重用 JSP 片段的理想方法。与页包含解决方案不同,标记文件专用于创建可重用的页片段库。本节比较了过时的 JSP 1.x 方法的语法与 JSP 2.0 标记文件的更好语法。
将被包含页变换为标记文件。让我们假设您具有一个名为 container.jsp 的页面,它利用 <jsp:include page="part.jsp"/> 包含了另一个名为 part.jsp 的页面。执行以下步骤,将 part.jsp 变换为标记文件。
第 1 步:移动被包含页。
在您的应用程序的 WEB-INF 目录中,创建一个名为 tags 的子目录,并将 part.jsp 页移动到 WEB-INF/tags 中。
第 2 步:重命名被包含页。
由于标准的标记文件扩展名是 .tag,您必须将 part.jsp 重命名为 part.tag。
第 3 步:编辑标记文件。
打开 part.tag 文件进行编辑,将 <%@page%> 指示语句替换为类似的 <%@tag%> 指示语句。如果遇到编译错误,则可能需要进行其他一些小的修改。使用 jspContext 替代 pageContext。
第 4 步:声明标记库。
在 container.jsp 的开头插入 <%@taglib prefix="tags" tagdir="/WEB-INF/tags"%>。
第 5 步:调用标记文件。
在 container.jsp 中将 <jsp:include page="part.jsp"/> 替换为 <tags:part/>。
container.jsp(变换前)
<p> Container Page </p> <jsp:include page="part.jsp"/>
container.jsp(变换后)
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <p> Container Page </p> <tags:part/>
part.jsp(变换前)
<%@ page language="java" %> <p> Included Page </p> <%= pageContext.getClass().getName() %>
part.tag(变换后)
<%@ tag language="java" %> <p> Invoked tag </p> <%= jspContext.getClass().getName() %>
基于 <jsp:include> 和 <%@include%> 的示例。本示例的主页 (main.jsp) 使用 <%@include%> 指示语句包含了两个页片段(header.jspf 和 footer.jspf)。主页还通过使用 <jsp:include> 来包含另一个页面 (content.jsp)。main.jsp 页利用 <jsp:param> 标准操作,向被包含页提供了三个参数(a、b 和 c)。被包含的 content.jsp 页利用 ${param.a}、${param.b} 和 ${param.c} 输出三个参数的值。
main.jsp
<%@ include file="header.jspf" %> <jsp:include page="content.jsp"> <jsp:param name="a" value="1"/> <jsp:param name="b" value="2"/> <jsp:param name="c" value="3"/> </jsp:include> <%@ include file="footer.jspf" %>
header.jspf
<p> Header </p>
footer.jspf
<p> Footer </p>
content.jsp
<p> Included Content </p> <p> Parameters - ${param.a}, ${param.b}, ${param.c} </p>
输出
基于标记文件的示例。本示例的 JSP 页 (main.jsp) 使用 <%@taglib%> 指示语句来声明包含标记文件 (wrapper.tag) 的库,指出其前缀 (tags) 以及创建标记文件的目录 (/WEB-INF/tags)。main.jsp 页使用 <tags:wrapper> 自定义标记来调用 wrapper.tag 文件,该标记具有三个属性(a、b 和 c)和一些主体内容 (<p> Wrapped Content </p>)。注意,您在 JSP 页的 <tags:wrapper> 与 </tags:wrapper> 之间可以不使用任何脚本编制元素。脚本编制元素包括 JSP 1.x 声明 (<%!...%>)、JSP 1.x 表达式 (<%=...%>) 和 scriptlet (<%...%>),而 JSP 2.0 支持所有这些元素。注意,标记文件可以象任何常规 JSP 页一样使用脚本编制元素。实际上,如果您不喜欢基于 Java 标记处理程序来创建 JSP 库,那么将 Java 代码从现有的 JSP 页移到标记文件中会是个不错的办法。这会使您的 Java scriptlet 可以被重用,并使您的 Web 页更易读。JSP 容器为您完成繁重的工作,它自动生成标记处理程序类。
wrapper.tag 文件是前面示例中三个被包含页(header.jspf、footer.jspf 和 content.jsp)的替代品。标记文件使用 <%@attribute%> 指示语句声明三个属性,并输出一个标题、一个页脚以及属性的值(${a}、${b} 和 ${c})。wrapper.tag 文件利用新的 <jsp:doBody> 标准操作,执行 main.jsp 的 <tags:wrapper> 的主体。标记文件决定是否执行以及何时执行 <tags:wrapper> 的主体。如果 wrapper.tag 不使用 <jsp:doBody>,则忽略 <tags:wrapper> 的主体。标记文件还可以多次使用 <jsp:doBody>,以便多次执行该主体。
main.jsp
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <tags:wrapper a="1" b="2" c="3"> <p> Wrapped Content </p> </tags:wrapper>
wrapper.tag
<%@ tag body-content="scriptless" %> <%@ attribute name="a" required="true" %> <%@ attribute name="b" required="true" %> <%@ attribute name="c" required="true" %> <p> Header </p> <jsp:doBody/> <p> Attributes - ${a}, ${b}, ${c} </p> <p> Footer </p>
输出
标记文件对比于 <jsp:include> 和 <%@include%>。调用标记文件的自定义标记具有比 <jsp:include> 与 <jsp:param> 的组合更紧密的语法。自定义标记还能以自然的方式进行嵌套和缩进,使得代码易于阅读。唯一可能感觉不便的方面是一项限制,不允许您在调用标记文件的自定义标记主体中使用脚本编制元素。考虑到无脚本的 Web 页可能更易于维护,实际上这也许是有利条件。如果您必须将 JSP 和 Java 代码混合在一起,则可以将 Java scriptlet 移到标记文件中。
<%@tag%>、<%@attribute%> 和 <%@variable%> 指示语句提供了更多的标记文件的好处:
- 属性声明使得标记文件的代码更易读,因为您只需通过查看标记文件的标题即可找到属性的名称。对于被包含页,没有在页面的开始部分声明参数。如果您必须使用别人开发的页面,并且没有为参数制作文档,则您可能必须分析整个页面的源代码。
- 在默认情况下,所有的属性都是可选项。如果需要某个属性,您可以使用 <%@attribute name="..." required="true"%> 来指定它。在默认情况下,调用标记文件的自定义标记可以包含无脚本的 JSP 代码。如果标记文件没有使用 <jsp:doBody>,您应该使用 <%@tag body-content="empty"%> 指定该标记文件忽略主体内容。这种声明防止意外使用在 <prefix:tagFileName> 与 </prefix:tagFileName> 之间 JSP 内容。在这种情况下,应该利用如 <prefix:tagFileName/> 的空标记来调用标记文件。通过指示所需的属性以及是否使用主体内容,您可以防止很多常见错误,减少了调试 JSP 页所花费的时间。对被包含的 JSP 页不提供这种声明支持。
- 也可以使用类似 <%@attribute type="className"%> 的语句来指定属性的类型。属性值将会被自动转换为指定类型,如果转换失败,会出现一个错误。如果是被包含页,所有的参数都是字符串,必须编写所有必需的转换。
- 新的 <%@variable%> 指示语句提供了对 JSP 容器的提示,该容器自动执行诸如 JSP 变量的导出和同步等任务,这有助于被调用标记文件与调用页之间的通信。我们稍后将在本文中了解它是如何工作的。如果是被包含的 JSP 页,必须手动实现页间的通信,这通常需要更多的编程操作。
- <%@attribute%> 和 <%@variable%> 指示语句支持那些在被包含 JSP 页中不能使用的特性,如在 java.util.Map 中收集的动态属性、表示 JSP 片段的属性以及名称由属性所指定的变量。下一节中的示例使用了这些特性。
使用高级 JSP 2.0 特性
以下示例显示如何开发 JSP 标记文件的真正功能。您将了解如何处理动态属性、如何将变量作为输出参数使用、如何使用属性来命名变量以及如何使用属性将 JSP 片段传递给标记文件。 Then, we'll use these advanced JSP features to build another set of tag files that query and update a database using the JSP expression language within SQL statements.
在标记文件中处理动态属性。本示例包含一个标记文件 (dynamicAttr.tag),该标记文件接受动态属性以及利用 <%@attribute> 指示语句所声明的几个其他属性(listTag、itemTag 和 separator)。一个 JSP 页 (dynamicAttr.jsp) 使用 <demo:dynamicAttr> 标记来调用标记文件。被声明的属性与动态属性可以混合在一起,因为属性的顺序并不重要。
在默认情况下,标记文件只接受被声明的属性。为了能够支持动态属性,标记文件必须使用 <%@tag dynamic-attributes="varName"%>,提供一个保持 java.util.Map 中所有动态属性的变量名。在我们的示例中,该变量被命名为 attrMap。dynamicAttr.tag 文件利用 JSTL 的 <c:forEach> 操作对 attrMap 的项目进行迭代,利用 ${attr.key} 和 ${attr.value} 获得每个属性的名称和值。
如果您查看输出,会发现六个动态属性(first、second、…sixth)不是以 dynamicAttr.jsp 中 <demo:dynamicAttr> 标记所指定的顺序而获得的。这种情况是因为 java.util.Map 实施控制着其项目的顺序。因此,必须对标记文件进行准备,以便以任意顺序处理其动态属性。
dynamicAttr.tag 文件使用 <jsp:element> 标准操作,用于生成那些在运行时获得其名称的 HTML 元素。这种新的 JSP 2.0 特性与动态属性无关,但我们在本例中使用它来生成 HTML 列表(listTag 为“ul”,而 itemTag 为“li”)。
dynamicAttr.tag
<%@ tag dynamic-attributes="attrMap" %> <%@ attribute name="listTag" required="true" %> <%@ attribute name="itemTag" required="true" %> <%@ attribute name="separator" required="true" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <p> Dynamic Attributes:</p> <jsp:element name="${listTag}"> <c:forEach var="attr" items="${attrMap}"> <jsp:element name="${itemTag}"> ${attr.key} ${separator} ${attr.value} </jsp:element> </c:forEach> </jsp:element>
dynamicAttr.jsp
<%@ taglib prefix="demo" tagdir="/WEB-INF/tags/demo" %> <demo:dynamicAttr separator="=" first="1" second="2" third="3" listTag="ul" itemTag="li" fourth="4" fifth="5" sixth="6"/>
输出
从标记文件导出变量到 JSP 页。JSTL 的 <c:set> 标记允许您在四种 JSP 的范围(页面、请求、会话和应用程序)中创建和更改变量。JSP 页与那些从 JSP 页所调用的标记文件共享请求、会话和应用程序范围。页面范围是 <c:set> 的默认范围,它并不共享。因此,您可以将那些在请求、会话和应用程序范围内创建的变量看作“全局变量”,而将那些在页面范围内创建的变量看作“本地变量”。
由于每个标记文件都有自己的页面范围,在被调用的标记文件中不能访问调用页的本地变量,反之亦然。JSP 页可以使用标记属性和全局变量,将参数传递到被调用的标记。对于每个属性,JSP 容器在标记文件的页面范围内创建一个本地变量,允许您使用 ${attributeName} 结构来获得属性的值。因此,标记属性充当由值所传递的参数,而全局变量充当由引用所传递的参数。如果标记文件更改了全局变量的值,则这种更改将影响 JSP 页。
标记文件可以使用 <%@variable%> 指示语句将其本地变量“导出”到调用页。可以在标记文件中使用 <%@variable name-given="..."%> 指定变量的名称,而 JSP 页也可以提供变量名,我们将在 varAttr.jsp 示例中看到这种情况。变量的类型可以利用 <%@variable variable-class="..."%> 来指示。
当利用 <%@variable%> 在标记文件中声明变量后,您必须使用 <c:set> 来设置该变量的值。JSP 容器利用在 JSP 页中给定的名称创建另一个变量,而这第二个变量是利用标记文件中的相应变量值进行初始化的。更改 JSP 页的变量值不会影响标记文件中的相应变量。但是,JSP 容器根据 <%@variable%> 的范围属性来更新 JSP 页的变量,该属性可能是 AT_BEGIN、NESTED 或 AT_END。
如果 scope 是 AT_END,则利用标记文件执行之后的标记文件变量值来初始化 JSP 页的变量。如果 scope 是 AT_BEGIN,则在 <jsp:doBody> 之前、在每个 <jsp:invoke> 之前以及在标记文件的执行结束时更新 JSP 页的变量。如果 scope 是 NESTED,则仅在 <jsp:doBody> 之前和每个 <jsp:invoke> 之前利用标记文件变量的值来更新 JSP 变量。在标记文件执行后,如果是 NESTED 的情况,则将 JSP 变量恢复到它在调用标记文件之前所具有的值。
为了更好地理解变量如何工作,让我们在一个示例中来使用它们,该示例包括一个标记文件 (varScope.tag)、一个 JSP 页 (varScope.jsp) 以及几个片段(varScopeRow.tagf、varScopeHeader.jspf、varScopeFooter.jspf 和 varScopeRow.jspf)。这些片段只用于创建一个 HTML 表。我们在本示例中研究标记文件变量时,必须使用 <%@include%> 将逻辑与表达相分离。在多数情况下,使用标记文件进行表达是最好的解决方案,但在本次情况中,额外的标记文件会干扰我们要研究的变量“导出”机制。
标记文件使用了一个“全局”变量 (GV)、一个未声明的“本地”变量 (LV) 和三个利用 <%@variable%> 所声明的变量(BV、NV 和 EV)。varScope.tag 文件利用“TAG A”对这些变量进行初始化,然后使用 <jsp:doBody>,最后将所有变量的值都改为“TAG B”字符串。varScope.jsp 页利用“JSP A”对其自己的一系列变量进行初始化,然后利用 <demo:varScope> 调用标记文件,在 <demo:varScope> 的主体内将变量的值改为“JSP B”。其结果输出包含在源代码的后面。
varScope.tag
<%@ variable name-given="BV" scope="AT_BEGIN" %> <%@ variable name-given="NV" scope="NESTED" %> <%@ variable name-given="EV" scope="AT_END" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:set var="tagPhase" value="TAG:at begin"/> <%@ include file="varScopeRow.tagf" %> <c:set var="GV" value="TAG A" scope="request"/> <c:set var="LV" value="TAG A"/> <c:set var="BV" value="TAG A"/> <c:set var="NV" value="TAG A"/> <c:set var="EV" value="TAG A"/> <c:set var="tagPhase" value="TAG:before doBody"/> <%@ include file="varScopeRow.tagf" %> <jsp:doBody/> <c:set var="tagPhase" value="TAG:after doBody"/> <%@ include file="varScopeRow.tagf" %> <c:set var="GV" value="TAG B" scope="request"/> <c:set var="LV" value="TAG B"/> <c:set var="BV" value="TAG B"/> <c:set var="NV" value="TAG B"/> <c:set var="EV" value="TAG B"/> <c:set var="tagPhase" value="TAG:at end"/> <%@ include file="varScopeRow.tagf" %>
varScopeRow.tagf
<tr> <td width="25%">${tagPhase}</td> <c:set var="allEqual" value= "${LV == GV and LV == BV and LV == NV and LV == EV}"/> <c:if test="${allEqual}"> <td align="center" width="75%" colspan="5"> <i> ${LV != null ?LV :"null"} </i> </td> </c:if> <c:if test="${!allEqual}"> <td align="center" width="15%"> ${GV != null ?GV :"null"} </td> <td align="center" width="15%"> ${LV != null ?LV :"null"} </td> <td align="center" width="15%"> ${BV != null ?BV :"null"} </td> <td align="center" width="15%"> ${NV != null ?NV :"null"} </td> <td align="center" width="15%"> ${EV != null ?EV :"null"} </td> </c:if> </tr>
varScope.jsp
<%@ taglib prefix="demo" tagdir="/WEB-INF/tags/demo" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ include file="varScopeHeader.jspf" %> <c:set var="GV" value="JSP A" scope="request"/> <c:set var="LV" value="JSP A"/> <c:set var="BV" value="JSP A"/> <c:set var="NV" value="JSP A"/> <c:set var="EV" value="JSP A"/> <c:set var="jspPhase" value="JSP:before start tag"/> <%@ include file="varScopeRow.jspf" %> <demo:varScope> <c:set var="jspPhase" value="JSP:after start tag"/> <%@ include file="varScopeRow.jspf" %> <c:set var="GV" value="JSP B" scope="request"/> <c:set var="LV" value="JSP B"/> <c:set var="BV" value="JSP B"/> <c:set var="NV" value="JSP B"/> <c:set var="EV" value="JSP B"/> <c:set var="jspPhase" value="JSP:before end tag"/> <%@ include file="varScopeRow.jspf" %> </demo:varScope> <c:set var="jspPhase" value="JSP:after end tag"/> <%@ include file="varScopeRow.jspf" %> <%@ include file="varScopeFooter.jspf" %>
varScopeHeader.jspf
<table border="1"> <tr> <th width="25%"> </th> <th align="center" width="15%"> GV<br>Global Var<br>in Request<br>Scope </th> <th align="center" width="15%"> LV<br>Local Var<br>Not<br>Declared </th> <th align="center" width="15%"> BV<br>Tag Var<br>Scope:<br>AT_BEGIN </th> <th align="center" width="15%"> NV<br>Tag Var<br>Scope:<br>NESTED </th> <th align="center" width="15%"> EV<br>Tag Var<br>Scope:<br>AT_END </th> </tr>
varScopeFooter.jspf
</table>
varScopeRow.jspf
<tr> <td width="25%">${jspPhase}</td> <c:set var="allEqual" value= "${LV == GV and LV == BV and LV == NV and LV == EV}"/> <c:if test="${allEqual}"> <td align="center" width="75%" colspan="5"> <i> ${LV != null ?LV :"null"} </i> </td> </c:if> <c:if test="${!allEqual}"> <td align="center" width="15%"> ${GV != null ?GV :"null"} </td> <td align="center" width="15%"> ${LV != null ?LV :"null"} </td> <td align="center" width="15%"> ${BV != null ?BV :"null"} </td> <td align="center" width="15%"> ${NV != null ?NV :"null"} </td> <td align="center" width="15%"> ${EV != null ?EV :"null"} </td> </c:if> </tr>
输出
使用属性为变量提供名称。前面的示例显示了如何使用变量,那些变量的名称是由标记文件给定的。下面的示例具有一个标记文件 (varAttr.tag),它创建一个变量,该变量名称由 JSP 页 (varAttr.jsp) 以标记属性的值 (v) 提供。为了能够操作此变量,标记文件使用 <%@variable name-from-attribute="v" ... alias="a" ...%> 定义了一个别名 (a)。
varAttr.tag 文件利用 <c:set var="a" value="..."/> 设置变量的值,利用 ${v} 输出变量的名称,并利用 ${a} 输出变量的值。varAttr.jsp 页利用 <demo:varAttr v="x"/> 调用标记文件,并使用命名为 x 的变量。注意,JSP 容器自动创建 x 变量,将其设为 a 的值,即 123。
varAttr.tag
<%@ attribute name="v" required="true" %> <%@ variable name-from-attribute="v" variable-class="java.lang.Long" alias="a" scope="AT_END" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:set var="a" value="${123}"/> <p> TAG:${v} = ${a} </p>
varAttr.jsp
<%@ taglib prefix="demo" tagdir="/WEB-INF/tags/demo" %> <demo:varAttr v="x"/> <p> JSP:x = ${x} </p>
输出
从标记文件调用 JSP 片段。目前您已经了解标记文件支持两种属性类型:简单声明的属性和动态属性。第三种类型称为片段属性。本示例的 JSP 页 (fragmentAttr.jsp) 利用 <demo:fragmentAttr> 调用标记文件 (fragmentAttr.tag),提供了两个简单的属性(attr1 和 attr2)和一个片段属性 (template)。全部三个属性都是利用 <%@attribute%> 在标记文件中声明的,但只有 template 利用 fragment="true" 被声明为片段属性。
fragmentAttr.jsp 页使用 <jsp:attribute> 和 <jsp:body> 标准操作,分别定义了两个利用 <jsp:invoke> 和 <jsp:doBody> 从标记文件中调用的 JSP 片段。此外,标记文件利用 <%@variable%> 声明了一个嵌套变量 (data)。该变量的值由标记文件利用 <c:set> 进行设置,而两个 JSP 片段利用 ${data} 输出该变量的值。 /P>
fragmentAttr.tag
<%@ attribute name="template" fragment="true" %> <%@ attribute name="attr1" %> <%@ attribute name="attr2" %> <%@ variable name-given="data" scope="NESTED" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:set var="data" value="${attr1}"/> <jsp:invoke fragment="template"/> <c:set var="data" value="${attr1} - ${attr2}"/> <jsp:doBody/> <c:set var="data" value="${attr2}"/> <jsp:invoke fragment="template"/> <c:set var="data" value="${attr2} - ${attr1}"/> <jsp:doBody/>
fragmentAttr.jsp
<%@ taglib prefix="demo" tagdir="/WEB-INF/tags/demo" %> <demo:fragmentAttr attr1="value1" attr2="value2"> <jsp:attribute name="template"> <p> Template:${data} </p> </jsp:attribute> <jsp:body> <p> Body Content:${data} </p> </jsp:body> </demo:fragmentAttr>
输出
使用标记文件更新和查询数据库
在开发复杂的企业应用程序时,很多人更喜欢使用企业 JavaBean (EJB) 技术,让应用服务器来管理对象可持续性。其他人可能更喜欢使用 Java 数据库连接 (JDBC) 标准 API,以便手动地优化数据库访问。这些解决方案可能不适合于简单的由数据库支持的网站,这种网站只需使用 JSP 尽可能快地进行原型开发。如果您只需要查询和更新一个简单的数据库,则提供数据库访问特性的标记库可能是最佳的解决方案。
JSTL 具有一系列简单的 SQL 标记,我们将在以下的示例中使用它们。不要直接从您的页面调用 JSTL SQL 标记,而使用标记文件将表达与数据库访问脚本相分离是个好办法。这种分离允许您以后能够方便地切换到另一个数据库连接解决方案,以便提高应用程序的性能和可伸缩性。
JSTL 不是唯一提供数据库访问特性的标记库。Oracle 应用程序开发框架 (ADF) 提供了一个称为商务组件数据标记的 JSP 库,它包含一系列与数据库相关的更加高级的 JSP 标记。注意,ADF 特性集的范围更广泛,使得 Oracle ADF 适合于简单的和复杂的应用程序。Oracle ADF 与 JDeveloper 10g 绑定在一起,后者具有用于 ADF 的可视设计工具。
以下的六个示例创建一个表、插入三行、更新一行、删除另一行、执行一些查询再删除该表。这一切都是利用通用标记文件完成的,这些标记文件不依赖于表的结构。
第 1 步:创建表。本示例的 JSP 页 (create.jsp) 调用标记文件 (create.tag) 来创建表,该表的结构以 <db:create> 自定义标记的主体内容提供。表的属性指定了表名 (People)。在创建表后,JSP 页输出一条消息,通知用户该操作已完成:
<db:create table="People"> userID INTEGER, name VARCHAR2(60), email VARCHAR2(60) </db:create> <p> Table created.</p>
create.tag 文件包含一个标记片段 (init.tagf),该片段利用 JSTL 的 <sql:setDataSource> 标记,在应用程序范围内设置一个名为 tags_db_dataSource 的 javax.sql.DataSource 变量。只有当该变量尚未存在,并且将要与 JSTL 的 <sql:update> 标记(该标记执行其主体中的 SQL 语句)一同在标记文件中使用时,才会创建该变量。create.tag 文件动态地创建 CREATE TABLE 语句,利用 ${table} 获取表名,并利用 <jsp:doBody> 插入表的结构:
<sql:update dataSource="${tags_db_dataSource}"> CREATE TABLE ${table} ( <jsp:doBody/> ) </sql:update>
为了运行示例,您必须配置一个名为 dbtags 的数据源。在 Web 应用程序的描述器 (web.xml) 中声明了对该资源的引用:
<resource-ref> <res-ref-name>jdbc/dbtags</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
init.tagf 片段从一个初始化参数那里获得资源的路径,这个初始化参数也是在 web.xml 中定义的:
<context-param> <param-name>tags_db_dataSource</param-name> <param-value>jdbc/dbtags</param-value> </context-param>
如果您要使用另一个数据源,只需在 web.xml 文件中将 dbtags 替换为您的数据源名称。
init.tagf
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> <c:if test="${applicationScope.tags_db_dataSource == null}"> <sql:setDataSource dataSource="${initParam.tags_db_dataSource}" var="tags_db_dataSource" scope="application"/> </c:if>
create.tag
<%@ tag body-content="scriptless" %> <%@ attribute name="table" required="true" %> <%@ include file="init.tagf" %> <sql:update dataSource="${tags_db_dataSource}"> CREATE TABLE ${table} ( <jsp:doBody/> ) </sql:update>
create.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:create table="People"> userID INTEGER, name VARCHAR2(60), email VARCHAR2(60) </db:create> <p> Table created.</p>
第 2 步:插入。在您拥有表之后,可以使用 <db:insert> 标记每次插入一行,该标记利用以下语法调用标记文件 (insert.tag):
<db:insert table="..." column1="value1" column2="value2" .../>
一个示例 JSP 页 (insert.jsp) 使用 <db:insert> 将三行插入到 People 表中。在每次调用时,标记文件使用以下语法创建和执行一条 SQL 语句:
INSERT INTO table (column1, column2, ...)VALUES (value1, value2, ...)
列名和值被指定为动态属性,因而 <db:insert> 可用于任何表,而无论表的列是如何命名的。标记文件使用 <c:forEach>,对 java.util.Map 实例 (columnAttr) 的项目进行迭代,该实例持有 <db:insert> 的动态属性。每个动态属性的名称表示一个列名,并且利用 ${v_entry.key} 而获得。标记文件利用 ${v_entry.value} 获得那些必须被插入到表中的值。
在 <sql:update> 的主体内,标记文件创建一个列名的列表 (v_columnNames) 和另一个包含参数标志的列表 (v_paramMarkers)。JSTL 的 <sql:param> 标记与传送 SQL 参数值的 <sql:update> 协作。在执行了不产生任何输出的 <c:forEach> 循环之后,标记文件利用以下语句产生 SQL 语句
INSERT INTO ${table} (${v_columnNames}) VALUES (${v_paramMarkers})
当 insert.jsp 调用标记文件时,<sql:update> 标记求出其主体所得的值
INSERT INTO People (userID, name, email) VALUES (?, ?, ?)
然后,<sql:update> 使用 JDBC API 并传递由 <sql:param> 所发送的参数值,执行以上的 SQL 语句。注意,列及其相应的值可能以不同的顺序出现,因为它们是从 java.util.Map 实例中检索出来的。因此,我们必须确保 SQL 语句不依赖于列的顺序。
insert.tag
<%@ tag body-content="empty" dynamic-attributes="columnAttr" %> <%@ attribute name="table" required="true" %> <%@ include file="init.tagf" %> <c:set var="v_columnNames" value=""/> <c:set var="v_paramMarkers" value=""/> <c:set var="v_separator" value=""/> <sql:update dataSource="${tags_db_dataSource}"> <c:forEach var="v_entry" items="${columnAttr}"> <sql:param value="${v_entry.value}"/> <c:set var="v_columnNames" value="${v_columnNames}${v_separator}${v_entry.key}"/> <c:set var="v_paramMarkers" value="${v_paramMarkers}${v_separator}${'?'}"/> <c:set var="v_separator" value=", "/> </c:forEach> INSERT INTO ${table} (${v_columnNames}) VALUES (${v_paramMarkers}) </sql:update>
insert.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:insert table="People" userID="1" name="John Smith" email="JohnSmith@company.com" /> <db:insert table="People" userID="2" name="Mark Johnson" email="MarkJohnson@company.com" /> <db:insert table="People" userID="3" name="Bill Davis" email="BillDavis@company.com" /> <p> Rows inserted.</p>
第 3 步:更新。update.jsp 页使用 <db:update> 来更改那些利用 insert.jsp 插入的行中的某一行的 email 值。update.tag 文件类似于 insert.tag,但此时 JSTL 的 <sql:update> 标记用于执行 UPDATE 语句:
UPDATE table SET column1=value1, column2=value2, ...WHERE ...
使用以下语法将列及其值指定为动态属性:
<db:update table="..." column1="value1" column2="value2" ... where="..."/>
标记文件使用动态属性来创建一个包含 column=? 结构的列表 (v_setList)。利用前面示例中的 <sql:param>,将列值传送到 <sql:update>。然后,update.tag 利用以下语句生成 SQL 语句
UPDATE ${table} SET ${v_setList} WHERE ${where}
当 update.jsp 调用标记文件时,<sql:update> 标记求出其主体所得的值
UPDATE People SET email=?WHERE userID=2
不同于每次只能用于插入一行的 <db:insert> 标记,<db:update> 标记可用于在单次调用中更新多行。
update.tag
使用以下资源来测试这些示例,并了解有关 JSP 2.0 标记文件以及 JSTL 核心和 SQL 标记的更多信息。 下载源代码 下载 OC4J 10g 下载 JSTL 1.1 阅读 JSP 2.0 规范 阅读 JSTL 1.1 规范 相关文章与下载 如何:向 JDeveloper 10g 添加自定义的 JSP 标记库 Oracle JDeveloper 10g |
<%@ tag body-content="empty" dynamic-attributes="columnAttr" %> <%@ attribute name="table" required="true" %> <%@ attribute name="where" required="false" %> <%@ include file="init.tagf" %> <c:set var="v_setList" value=""/> <c:set var="v_separator" value=""/> <sql:update dataSource="${tags_db_dataSource}"> <c:forEach var="v_entry" items="${columnAttr}"> <sql:param value="${v_entry.value}"/> <c:set var="v_setList" value="${v_setList}${v_separator}${v_entry.key}=?"/> <c:set var="v_separator" value=", "/> </c:forEach> UPDATE ${table} SET ${v_setList} <c:if test="${!empty where}"> WHERE ${where} </c:if> </sql:update>
update.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:update table="People" email="markj@company.com" where="userID=2"/> <p> Row updated.</p>
第 4 步:删除。以下的标记文件 (delete.tag) 可用于删除一行或多行,它位于 delete.jsp 页中。
delete.tag
<%@ tag body-content="empty" %> <%@ attribute name="table" required="true" %> <%@ attribute name="where" required="false" %> <%@ include file="init.tagf" %> <sql:update dataSource="${tags_db_dataSource}"> DELETE FROM ${table} <c:if test="${!empty where}"> WHERE ${where} </c:if> </sql:update>
delete.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:delete table="People" where="userID=1"/> <p> Row deleted.</p>
第 5 步:选择。下一个标记文件 (select.tag) 使用 JSTL 的 <sql:query> 标记来执行 SELECT 语句。<db:select> 的语法包括在下面:
<db:select var="row" table="..." columns="..." where="..." groupBy="..." having="..." orderBy="..."> process the result of the query, one row at a time </db:select>
<db:select> 标记创建和执行以下的 SQL 语句,其子句被指定为标记的属性。
SELECT columns FROM table WHERE ... GROUP BY ...HAVING ...ORDER BY ...
当 <db:select> 出现在 JSP 页中时,利用 select.tag 的 <jsp:doBody> 操作为每行调用它的主体。JSP 页从一个由标记文件与 JSTL 所创建的 java.util.Map 中获取行数据。例如,当前行的值可以利用类似 ${row.userID}、${row.name} 和 ${row.email} 的 JSP 结构而获得,此时假定 JSP 页调用的标记文件指定应该将持有数据的变量命名为 row:
<db:select var="row" table="People" columns="*"> ${row.userID} ...${row.name} ...${row.email} </db:select>
select.jsp 页两次调用了标记文件。首先它使用 <db:select> 来迭代关系表的行,生成一个 HTML 表,然后 JSP 页使用 <db:select> 来计算 People 表的行数。
select.tag
<%@ tag body-content="scriptless" %> <%@ attribute name="var" required="true" %> <%@ variable name-from-attribute="var" alias="v_row" scope="NESTED" %> <%@ attribute name="table" required="true" %> <%@ attribute name="columns" required="false" %> <%@ attribute name="where" required="false" %> <%@ attribute name="groupBy" required="false" %> <%@ attribute name="having" required="false" %> <%@ attribute name="orderBy" required="false" %> <%@ include file="init.tagf" %> <sql:query dataSource="${tags_db_dataSource}" var="v_result"> SELECT ${empty columns ?"*" : columns} FROM ${table} <c:if test="${!empty where}"> WHERE ${where} </c:if> <c:if test="${!empty groupBy}"> GROUP BY ${groupBy} </c:if> <c:if test="${!empty having}"> HAVING ${having} </c:if> <c:if test="${!empty orderBy}"> ORDER BY ${orderBy} </c:if> </sql:query> <c:forEach var="v_row" items="${v_result.rows}"> <jsp:doBody/> </c:forEach>
select.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="2" cellspacing="1" cellpadding="3"> <tr> <th> User ID </th> <th> Name </th> <th> Email </th> </tr> <db:select var="row" table="People" orderBy="name"> <tr> <td> ${row.userID} </td> <td> ${row.name} </td> <td> ${row.email} </td> </tr> </db:select> </table> <db:select var="c" table="People" columns="count(*) as n"> <p> ${c.n} people.</p> </db:select>
输出
第 6 步:删除表。如果您希望再次执行 JSP 示例,则必须利用 drop.jsp 页删除表,该页使用了 drop.tag 文件。
<%@ tag body-content="scriptless" %> <%@ attribute name="table" required="true" %> <%@ include file="init.tagf" %> <sql:update dataSource="${tags_db_dataSource}"> DROP TABLE ${table} </sql:update>
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:drop table="People"/> <p> Table dropped.</p>
第 6 步:删除表。如果您希望再次执行 JSP 示例,则必须利用 drop.jsp 页删除表,该页使用了 drop.tag 文件。
drop.tag
<%@ tag body-content="scriptless" %> <%@ attribute name="table" required="true" %> <%@ include file="init.tagf" %> <sql:update dataSource="${tags_db_dataSource}"> DROP TABLE ${table} </sql:update>
drop.jsp
<%@ taglib prefix="db" tagdir="/WEB-INF/tags/db" %> <db:drop table="People"/> <p> Table dropped.</p>
结论
标记文件是 Web 开发人员所需要的一种易于使用的解决方案,用于创建自定义标记库。从 Java 标记处理程序到 JSP 标记文件的转变类似于几年前出现的从 Servlet 到 JSP 的转变。在开发 Java Web 应用程序时,大部分动态内容现在是由 JSP 生成的,但 Servlet 仍然用作 JSP 的一种底层技术,并且在有些情况下 Servlet 比 JSP 更适用。同样,Java 标记处理程序仍然需要作为标记文件的一种底层技术,用于创建复杂的标记库,如 JSTL。但是,大多数标记库将使用标记文件来创建,因为标记文件简化了开发过程。