简介: JSP自定义标记为在动态 Web 页中将表示与业务逻辑分离提供了一种标准化的机制,使页面设计者可以将注意力放到表示上,而应用程序开发人员编写后端的代码。在 J2EE 探索者的这篇文章中,具有企业观念的开发者 Kyle Gabhart 介绍了 JSP 自定义标记的基本知识,并引导您完成将它们加入到JSP 页面的五步过程。
您可能听说现在有上百种不同的方式,但是在开发 Web 应用程序时将表示逻辑与业务逻辑分离是很重要的。近年来,Java 平台已经发展为在体系结构层次上加入了这种分离。例如,在 JSP 体系结构中加入 JavaBean 组件使开发人员使用 JSP 标记获得和设置经过特别编码的 Java 组件上的属性。这些组件或者 JavaBean 再代表表示层执行后端业务处理。
JSP 自定义标记是 JSP/JavaBean 体系结构的产物。像 JavaBean 技术一样,自定义标记有助于将表示逻辑与业务逻辑分离。并且自定义标记成了 Web 设计者的 HTML、XML 和 Javascript 世界与软件工程师的 Java 代码、SQL 调用和算法世界之间的桥梁。
在本月的 J2EE 探索者 中,您将学到在 JSP 页中使用 JSP 自定义标记的一些基本知识。我们首先介绍 JSP 标记,然后直接进入创建标记、将它结合到 JSP 容器并在 JSP 页面中使用它的过程。在本文的最后,我们将简要讨论 Java 标准标记库(Java Standard Tag Library, JSTL),这是一组可以在您的 Java 开发项目中使用的、标准化的 JSP 自定义标记。
JSP 自定义标记是用户定义的标记,它遵循 JSP JavaBean 标记(即 useBean
、 getProperty
和 setProperty
)所使用的一种特殊的 XML 语法。当 servlet 容器处理自定义标记时,会调用一个或者多个 Java 类文件处理它,与用 Java 类文件处理 JSP 页面的 JavaBean 调用的方式基本一样。处理标记以后,容器将取其名字和属性、以及标记正文中可能有的任何内容,并将它传递给一个或者多个类文件进行处理。
Java 开发人员编写标记处理程序类以处理标记并处理所有需要的 Java 代码和数据操作。对于 Web 设计者来说,自定义标记与标准 HTML 标记除了都可以利用后端动态数据外,它们看上去与使用起来没什么区别。正确编写自定义标记可以让 Web 设计者创建、查询和操作数据而无需编写一行 Java 代码。正确使用自定义标记使 Java 开发人员不必再在编码过程中考虑表示层。这样应用程序开发小组的每一位成员都可以关注于他或者她最擅长的事物。
JSP 体系结构需要以下组件以实现自定义标记:
- 在每一页中有一个 JSP 声明
- Web 应用程序描述符(web.xml)中的一个项
- 一个包含特殊 XML 文件和为处理自定义标记而调用的 Java 类的 JAR 文件
在下面几节,您将一步一步地学习如何满足这些要求并将自定义标记加入到 JSP 页面中。要想成功实现 JSP 自定义标记,您需要采取下面五个步骤:
- 编写标记处理程序类。
- 创建标记库描述符(TLD)。
- 使 TLD 文件和处理程序类可访问。
- 引用标记库。
- 在 JSP 页面中使用标记。
这些内容是相当基本的,也不用花很长时间。就让我们开始吧。
在下面的例子中,我们将使用一个非常简单的显示当前时间和日期的自定义标记例子。下面就是 DateTag
:
<abc:displayDate /> |
我们要做的第一件事是编写标记处理程序类。在执行引用自定义标记的 JSP 页面时,JSP 容器判断每一个自定义标记。当容器遇到一个标记时,它调用与这个自定义标记相关联的标记处理程序,我们将在后面更多地讨论这个过程。然后,每一个标记处理程序实现 JSP API 中的一个特殊接口。标记有两种类型:可以处理标记内容(或者正文)的标记和不能处理标记内容的标记:
<abc:tagWithNoBody attribute="value"/> <abc:tagWithBody attribute="value"> This is some body content that the tag handler can operate upon. </abc:tagWithBody> |
在 DateTag
例子中不需要加入正文内容,因为它只显示当前日期。因此,我们的处理程序类将实现 Tag
接口(一般是通过扩展TagSupport
类)。如果我们要创建一个可以处理正文的标记,那么我们就需要实现 BodyTag
接口(一般是通过扩展 BodyTagSupport
类)。清单 1 显示了 DateTag
的处理程序类:
清单 1. 标记处理程序类
package myTags; import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import javax.servlet.http.*; import java.text.*; import java.util.*; public DateTag extends TagSupport { public int doStartTag() throws javax.servlet.jsp.JspException { HttpServletRequest req; Locale locale; HttpJspPage g; DateFormat df; String date; JSPWriter out; req = ( HttpServletRequest )pageContext.getRequest(); locale = req.getLocale(); df = SimpleDateFormat.getDateInstance( SimpleDateFormat.FULL,locale ); date = df.format( new java.util.Date() ); try { out = pageContext.getOut(); out.print( date ); } catch( IOException ioe ) { throw new JspException( "I/O Error : " + ioe.getMessage() ); }//end try/catch return Tag.SKIP_BODY; }//end doStartTag() }//end DateTag |
关于 DateTag
处理程序类有几件事值得一说。首先看一下方法声明。如果我们直接实现 Tag
接口,那么就需要完成几个方法声明。因为 TagSupport
类是简单的、具体类,它完全实现了在 Tag
接口中声明的方法,我们可以只实现那些在自定义标记中要使用的方法。在这个简单的例子中,我们只实现了 doStartTag()
方法,它是在遇到开始标记时调用的。
您还可能注意到 doStartTag()
方法返回 SKIP_BODY
。当然其原因是我们的简单日期标记没有正文。您要注意的最后一件重要的事情是使用了 pageContext
对象访问输出缓存以直接向输出流发送内容。您可能还记得在本系列以前的文章说过, pageContext
对象是一个隐式对象,它提供对与当前页面有关的属性的访问。
编写了源代码文件后,我们像编译所有其他 Java 类一样编译这个类(确保在类路径中加入 Servlet/JSP JAR 文件),然后将编译的类文件放到 Web 应用程序的类目录(WEB-INF/classes)中。如果我们开发的是几个标记或者定义有标记变量的标记,那么我们就会有多个标记处理程序类。在这种情况下我们可能选择将处理程序类打包在一个 JAR 文件中而不是使它们成为类目录中的分散的文件。
下一步是定义包含自定义标记与处理它的 Java 类(或多个类)之间的映射的库。这个库是在一个名为标记库描述符(TLD)的 XML 文档中定义的。我们将从 DateTag
例子 DateTagLib.tld中调用这个 TLD。注意“.tld”是这种文件的标准扩展名。
清单 2. DateTagLib.tld 文件
<?xml version="1.0" encoding="ISO-8859-1" ?> <taglib> <tlibversion>1.0</tlibversion> <info>A simple tag library</info> <tag> <name>displayDate</name> <tagclass>myTags.DateTag</tagclass> <bodycontent>empty</bodycontent> <info>Display Date</info> </tag> </taglib> |
DateTagLib.tld 是一个出色的、最小的标记库描述符文件。所有关键信息都包含在 Tag
标记中,在这里映射了标记名和处理程序类,我们声明了标记对于正文内容的敏感性。对于更复杂的情况,我们可以使用其他的 XML 标记以提供有关库和标记的更多信息。在一个库中定义多个标记也很常见。
第 3 步是使这个类或者这些类和 TLD 可以被 Web 应用程序访问。有两种方法:可以将类和 TLD 打包到一个 JAR 文件中,再将这个 JAR 文件储存在 Web 应用程序的 lib 目录中,也可以将类文件分散地放到 classes 子目录中并将 TLD 文件放到 Web 应用程序的 WEB-INF 目录下面的某一位置。
在这个例子中,我们将使用第二种方法,将 TLD 文件和类分散地放到 Web 应用程序目录结构中。您可以回忆起在第 1 步中我们已经将标记处理程序类放到了 classes 目录中,所以我们实际上只需储存 TLD 文件。TLD 文件被储存在 WEB-INF 目录或者子目录中,如果是部署 Java 文件,则储存在 JAR 的 META-INF/ 目录或者子目录。在这里,我们没有使用 JAR 文件,所以我们只将 TLD 储存到 Web 应用程序的 WEB-INF/lib 目录中。
这时,我们已经编译了标记处理程序类、创建了 TLD 文件以定义处理程序类和标记之间的映射、并保证类和标记在应用程序中都是可访问的。下一步是建立 JSP 页面与标记库之间的引用。有两种方法声明 JSP 页面与其库之间的引用。可以通过 Web 应用程序描述符(web.xml)声明一个静态引用,也可以直接在页面中声明一个动态引用。我们将试用这两种方法。
为了进行静态引用,首先必须将下面的项加入到 web.xml 文件中:
<?xml version="1.0" encoding="ISO-8859-1" ?> <Web-app> <!-- Define Servlets, Servlet Mappings, etc. --> <taglib> <taglib-uri>myTags</taglib-uri> <taglib-location>/WEB-INF/lib/DateTagLib.tld</taglib-location> </taglib> </Web-app> |
然后,将 JSP 声明加入到所有需要使用自定义标记库的页面中:
<%@ taglib uri="myTags" prefix="abc" %> |
注意指定的 uri
属性与在 web.xml 文件中指定的 taglib-uri
值相匹配。
为了进行动态引用,只需在所有需要使用这个库的页面中加入一个 JSP 声明即可:
<%@ taglib uri="/WEB-INF/lib/DateTagLib.tld" prefix="abc" %> |
在进行标记库的静态引用时,JSP 声明必须查询 web.xml 文件以执行库查询。这意味着如果移动或者重命名了库,或者希望在 web.xml 文件中加入更多的库,就必须停止服务器、更新 web.xml 文件、然后重新启动服务器。动态方法让 JSP 页直接指向 TLD 位置,因而是在解释 JSP 页面时进行处理。
静态方法提供了页面与库的实际名和位置之间一定程度的非直接性,这可以为您提供一些改变这些属性而不修改页面的灵活性。另一方面,动态方法提供了更大的灵活性,让您可以在运行时增加和移动标记声明。如果您对动态方法感兴趣,但是又担心做了一些改变后、有可能要更新多个页面的维护负担,那么您可以始终将 JSP 声明放到一个单独的 JSP 文件中,并在每一个要访问 Web 应用程序的自定义库的页面中加入这一页。这使您具有在运行时只需要更新信息一次就可以增加库的灵活性。
完成了所有这些准备工作后,我们就可以在 JSP 页面中使用这些自定义标记了。清单 3 显示了包含 DateTag
的 JSP 页面的浏览器输出:
清单 3. 带有自定义标记的 JSP 页
<%@ taglib uri="/WEB-INF/lib/DateTagLib.tld" prefix="abc" %> <HTML> <HEAD> <TITLE>Date tag example</TITLE> </HEAD> <BODY> <H1>Date tag Example</H1> <p>Hi today is <b><abc:displayDate /></b> </p> </BODY> </HTML> |
重新启动 Web 服务器并测试自己的 JSP 页面 !结果看起来类似于图 1 所示。
图 1. DateTag 的浏览器输出
您所需要的自定义标记功能中也许有多达百分之八十已经由 J2EE 团体创建并标准化了。使用现有的标记库而不是从头创建所有东西会使您节省大量时间和精力。尽管在公共域有数十种库,不过业界汇集了一个特定的自定义库。Java 标准标记库(JSTL)是由 Java Community Process 设计的,其参考实现是由 Apache Group 通过 Jakarta Taglibs 项目所开发和维护的(参阅 参考资料)。
JSTL 定义了针对常见 Web 应用程序处理需求,如变量支持、流程控制、URL 管理、XML 操作、国际化、数据库访问等等的标记。除了一组丰富的标记外,JSTL 还定义了自己的表达式语言(EL)。EL 使我们可以容易地访问应用程序数据并更容易在不使用脚本或者请求时表达式的条件下操作这些数据。
除了节省您从头开发所有标记的时间和精力,JSTL 还具有标准化和业界承认的所有好处。这些好处包括厂商支持、大量介绍文字、以及有很大的机会找到具有 JSTL 经验的雇员或者承包商。
在 J2EE Web 开发中越来越多地需要将业务和表示逻辑分离,JSP 自定义标记提供了替代简单的老 JavaBean 和 Java 脚本的一个有吸引力的方法。更好的是在 JSTL 中已存在一组已定义的标准的自定义标记库。在 J2EE 探索者 的这篇文章中,您经历了从头创建一个自定义标记并在 JSP 页面中实现它的过程。我还简单介绍了 JSTL 并阐述了使用它而不是从头创建所有的自定义标记的好处。
下个月,我们将继续探索 J2EE 技术,对 Web 应用程序安全体系结构进行分析。祝这段时间里探索快乐!
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 参与有关本文的 讨论论坛(您也可以单击文章顶部或底部的 讨论来访问论坛)。
- Jakarta Taglibs Project 是 Java 标准标记库 1.2 的家。
- Ken Wilson 的“ I Love JSP Custom Tags!”页面从经常使用 JSP 自定义标记的 Web 设计员的视角对它做了有趣的介绍。
- Brett McLaughlin 最近的“ JSP 最佳实践:taglib简介”( developerWorks,2003 年 7 月)为您展示了如何将 scriptlet 转换为 JSP 自定义标记。
- Mark Kolb 的四部分的 JSTL 入门:表达式语言( developerWorks,2003 年 2 月 - 6月)提供了您需要了解的所有 JSP 自定义标记和 JSTL 的内容。
- 在他的“ Professional JSP”一书中的一段内容中,作者 Dan Malks 解释了分离业务逻辑与表示逻辑的重要性,以及 Java 平台体系结构在支持这种分离方面的进展。
- 本系列以前文章的完整列表见 J2EE 探索者系列 。
- 在 developerWorks Java 技术专区 可以找到关于 Java 编程各个方面的数百篇文章。
Kyle Gabhart 是一名自由顾问,他是 J2EE、XML 和 Web 服务技术方面的专家。 由于他的激情和对新兴技术的动态分析和演示,使得 Kyle 成为一位深受大家欢迎的演讲者。要获取有关他最近以及即将进行的演讲或即将发表的业内出版物的信息,请访问 Gabhart.com。可以通过 kyle@gabhart.com与他联系。