posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Jsp 自定义标签

Posted on 2007-06-26 19:19 chenweicai 阅读(1941) 评论(0)  编辑  收藏
本教程目的 第 1 页(共3 页)
                                                                                        

想要在 JavaServer Pages (JSP) 应用程序中添加自定义标签吗?本教程将为您展示如何用这些标签编写类似于 JSP 技术自带操作 —— 如 jsp:useBeanjsp:getPropertyjsp:forward —— 的自定义操作。介绍如何用特定于自已的域的表示逻辑的自定义操作来扩展 JSP 语法。

在 JSP 应用程序中添加 自定义标签 的能力可以使您将工作重点放到以文档为中心的开发方式上。可以使 Java 代码不出现在 JSP 页中,从而使这些页面更容易维护。(我从经验中学到,在 JSP 页中放入过多的 Java 代码时,代码维护就会成为可怕的任务)。本教程将使您可以立即开发出自定义标签。了解了 JSP 自定义标签开发的好处后,您可能会对程序员没有更多地使用它而感到意外。

在本教程中,我将讨论使用自定义标签的基本内容。将介绍如何用自定义标签创建可重用的表示组件并避免在 JSP 页加入 Java scriptlet。

在本教程中,我们将:

  • 定义一个 JSP 自定义标签体系结构。
  • 解释简单标签。
  • 定义嵌套标签。
  • BodyContent 解释标签。
  • 在标签中添加属性。
  • 在标签中添加 scriptlet 变量。
  • 用自定义标签实现控制流程。
  • 用 Struts 简化标签部署。

我要学习本教程吗? 第 2 页(共3 页)


如果发现自己在 JSP 应用程序中加入了大量 Java scriptlet,那么本教程就是为您准备的。 阅读本教程后,就会掌握将 Java 代码从 JSP 页面中清除出去所需要的信息。

本教程假定读者熟悉 Java 平台、JavaServer Pages (JSP) 技术、MVC 模式、Reflection API、Model 2,最好还有 Struts 框架。此外,要从本教程中得到最大的收获,还需要很好的使用标签库的经验

关于作者 第 3 页(共3 页)


Rick Hightower 是一位 J2EE 开发人员和顾问,他热衷于使用 J2EE、Ant、Hibernate、Struts、IMB 的 ETTK 和 Xdoclet。 Rick 是 Trivera Technologies 的前任 CTO,这是一家全球培训、指导和咨询公司,其重点是企业开发。他经常在 IBM developerWorks 上发表文章,并编写了 10 多篇 developerWorks 教程,内容从 EJB 技术到 Web 服务到 XDoclet。 Rick 不久前与别人共同开办了另一家名为 ArcMind 的公司,它专门研究 agile 方法,还从事 Struts/JavaServer Faces 开发、咨询和指导。

在为 eBlox 工作时,Rick 和 eBlox 小组远在 1.0 版本之前就已使用 Struts 为电子商务站点构建了两个框架和一个 ASP (应用程序服务提供者)。这个框架目前正在为 2000 多个在线商店店面提供支持。

Rick 最近完成了一本名为 Professional Jakarta Struts 的书。在周游全国对 J2EE 和 Struts 项目提供咨询,或者在大会上发表关于 J2EE 和极端编程 (extreme programing)的讲演之余,Rick 喜欢在通宵咖啡店喝咖啡,写一些有关 Struts、J2EE 和其他内容的文章,并以第三人称描写他自己。

标签处理程序 第 1 页(共2 页)


在创建自定义标签之前,需要创建一个 标签处理程序。标签处理程序是一个执行自定义标签操作的 Java 对象。在使用自定义标签时,要导入一个 标签库 —— 即一组标签/标签处理程序对。通过在 Web 部署描述符中声明库导入它,然后用指令 taglib 将它导入 JSP 页。

如果 JSP 容器在转换时遇到了自定义标签,那么它就检查 标签库描述符(tag library descriptor) (TLD) 文件以查询相应的标签处理程序。TLD 文件对于自定义标签处理程序,就像 Web 部署描述符对于 servlet 一样。

在运行时,JSP 页生成的 servlet 得到对应于这一页面所使用的标签的标签处理程序的一个实例。生成的 servlet 用传递给它的属性初始化标签处理程序。

标签处理程序实现了 生存周期 方法。生成的 servlet 用这些方法通知标签处理程序应当启动、停止或者重复自定义标签操作。生成的 servlet 调用这些生存周期方法执行标签的功能。

标签的类型 第 2 页(共2 页)


可以定义两种类型的标签:

  • javax.servlet.jsp.tagext.Tag
  • javax.servlet.jsp.tagext.BodyTag

正文 进行操作 —— 即对在开始和结束标签之间的内容进行操作的 —— 标签必须实现 BodyTag 接口。在这个教程中,我们将称这些标签为 正文标签。我们将不对其正文操作的标签称为 简单标签。简单标签可以实现 Tag 接口,尽管不要求它们这样做。要记住不对其正文操作的标签仍然 正文,只不过,它的标签处理程序不能读取这个正文。

简单标签的例子 第 1 页(共9 页)
                                                                                                   上一页      下一页

Struts 框架带有几个自定义标签库(有关 Struts 的更多信息的链接请参阅 参考资料 )。这些库中的一个标签可以创建一个支持改写 URL 的链接并用 jsessionid 对改写的连接编码。

不过有一个问题:如果希望传递一组请求参数(如查询字符串),也许必须为此创建一个 Java scriptlet。真是乱!下面的清单 (search_results.jap) 展示了一个 JSP 页,它被迫加入了这样一个 scriptlet。

 <%@ taglib uri="struts-html" prefix="html" %> <jsp:useBean class="java.util.HashMap" id="deleteParams" /> <% deleteParams.put("id", cd.getId()); deleteParams.put("method","delete"); %> <!-- Pass the map named deleteParams to html:link to generate the request parameters--> <html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  

search_results.jsp 创建一个 hashmap 并向这个 map 传递两个属性。在下面几小节,我们将创建一个不用 Java 代码完成这项工作的自定义标签。我们的标签将定义如下的一个 hashmap:

 <map:mapDefine id="deleteParams"> <map:mapEntry id="id" name="cd" property="id"/> <map:mapEntry id="method" value="delete"/> </map:mapDefine> <!-- Pass the map named deleteParams to html:link to generate the request parameters--> <html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  

这将使我们可以容易地创建小型 map。

这个例子将展示几个关键概念,包括使用嵌套标签和定义 scriplet 变量。首先我将解释这个标签是如何工作的。然后在以后的几节中建立这些概念,并介绍如何编写这个标签的不同形式,使它们处理其正文并控制执行流程。

构建简单标签的步骤 第 2 页(共9 页)


让我们创建一个定义一个 HashMap scriptlet 变量的标签。为此,需要实现标签处理程序接口 (javax.servlet.jsp.tagext.Tag)。因此,我们要创建的第一个标签将是一个简单标签。

这个标签将实例化一个 map。使用这个标签的开发人员可以指定要实例化的 map 的类型 —— HashMapTreeMapFastHashMap 或者 FastTreeMapFastHashMapFastTreeMap 来自 Jakarta Commons Collection library (有关链接请参阅 参考资料)。开发人员还可以指定标签所在的范围 —— 页、请求、会话还是应用程序范围。

要构建这个简单标签,我们需要完成以下步骤:

  1. 创建实现了 Tag 接口(准确地说是 javax.servlet.jsp.tagext.Tag)的标签处理程序类。

  2. 创建一个 TLD 文件。

  3. 在标签处理程序 Java 类中创建属性。

  4. 在 TLD 文件中定义与标签处理程序 Java 类中定义的属性对应的属性。

  5. 在 TLD 文件中声明 scriptlet 变量。

  6. 实现 doStartTag() 方法。在标签处理程序类中,根据属性将值设置到 scriptlet 变量中。

如果您像我一样,可能会提前阅读书的结尾,所以请查看 附录 中标签处理程序类的完整列表以了解这个过程是如何结束的。

在下面几小节中,我们将分析 MapDefineTag 的实现,并分析如何到达这一步。

第 1 步:创建一个实现了 Tag 接口的标签处理程序 第 3 页(共9 页)


为了编写标签处理程序,必须实现 Tag 接口。如前所述,这个接口用于不操纵其标签正文的简单标签处理程序。就像 J2EE API 文档 (有关链接请参阅 参考资料)所说的:Tag 接口定义了标签处理程序和 JSP 页实现类之间的基本协议。它定义了在标签开始和结束时调用的生存周期和方法。

标签处理程序接口有以下方法:

方法 作用
int doStartTag() throws JspException 处理开始标签
int doEndTag() throws JspException 处理结束标签
Tag getParent()/void setParent(Tag t) 获得/设置标签的父标签
void setPageContext(PageContext pc) pageContext 属性的 setter 方法
void release() 释放获得的所有资源

TagSupport

现在,不必直接实现 Tag 接口,相反,用 map 定义的(map-defining)标签将继承 TagSupport 类。这个类以有意义的默认方法实现 Tag 接口,因而使开发自定义标签更容易 (有关 TagSupport 的 API 文档的链接请参阅 参考资料)。 例如,TagSupport 类定义了 get/setParent()setPageContext(),这与所有标签处理程序几乎相同。 get/setParent() 方法允许标签嵌套。TagSupport 类还定义了一个可以被子类使用的 pageContext 实例变量 (protected PageContext pageContext),这个变量是由 setPageContext() 方法设置的。

在默认情况下,TagSupport 实现了 doStartTag() 以使它返回 SKIP_BODY 常量,表示将不对标签正文进行判断。 此外,在默认情况下,doEndTag() 方法返回 EVAL_PAGE,它表示 JSP 运行时引擎应当对页面的其余部分进行判断。 最后,TagSupport 实现了 release(),它设置 pageContext 及其父元素为 null

TagSupport 类还实现了 IterationTag 接口和 doAfterBody(),这样它就返回 SKIP_BODY。 在后面讨论进行迭代的标签时我将对此加以更详细的解释(请参阅 用自定义标签控制流程)。

好了,现在让我们通过继承 TagSupport 来实现 Tag 接口:

 ... import javax.servlet.jsp.tagext.TagSupport; ... public class MapDefineTag extends TagSupport { ...  

我们已经定义了标签处理程序,现在需要增加从处理程序到 TLD 文件中的标签的映射。我们将在下一小节中对此进行处理。然后,将完成 MapDefineTag 中剩余的代码。

第 2 步:创建一个 TLD 文件 第 4 页(共9 页)


TLD 文件对自定义标签处理程序的作用就像 Web 部署描述符对 servlet 的作用。 TLD 文件列出了从标签名到标签处理程序的映射。 这个文件中的大多数数据都是在 JSP 页转换时使用的。 TLD 文件通常保存在 Web 应用程序的 WEB-INF 目录,并在 web.xml 文件中声明。它们一般用 .tld 扩展名结束。

TLD 文件有一个 导言(preamble),在这里标识 JSP 技术的版本和使用的标签库。这个导言通常看起来像这样:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>map</short-name>  

让我们更详细地分析一下这些标签:

  • TLD 文件的根元素是 taglibtaglib 描述了一个 标签库 —— 即一组标签/标签处理程序对。

  • 因为我们使用的是 JSP 版本 1.2,所以在这个例子中需要 tlib-versionshort-name 元素。

  • tlib-version 元素对应于标签库版本。

  • jsp-version 对应于标签库所依赖的 JSP 技术的版本。

  • short-name 元素定义了 IDE 和其他开发工具可以使用的标签库的简单名。

  • taglib 元素包含许多 tag 元素,标签库中每一个标签有一个 tag 元素。

因为我们刚创建了自己的类,所以我们将继续往下进行,在 TLD 文件中声明这个类,如下所示:

 <taglib> 
...
<tag>
<name>mapDefine</name>
<tag-class>trivera.tags.map.MapDefineTag</tag-class>
<body-content>JSP</body-content>
...

tag 元素用于将自定义标签映射到它们的自定义标签处理程序。上述清单中的 tag 元素将自定义标签 mapDefine 映射到处理程序 trivera.tags.map.MapDefineTag。 因此,不论在 mapDefine 上运行的是什么转换引擎,都会调用 trivera.tags.map.MapDefineTag

已经在 TLD 中定义了标签,接下来要在标签处理程序类中定义这个标签的一些属性了。

第 3 步:在标签处理程序 Java 类中创建属性 第 5 页(共9 页)
                                                                                                               上一页      下一页

我们希望为 mapDefine 标签指定三个属性,如下所示:

属性说明
id 新 scriptlet 变量的名字。
scope 新 scriptlet 变量所在的范围。
type 新 scriptlet 变量的类型 (HashMapFastHashMapTreeMap 或者 FastTreeMap)。 如果 type 设置为 hash,那么就会创建一个 HashMap。如果 type 设置为 fasthash,那么将创建 FastHashMap

在 JSP 页中使用这个标签时,它看起来将像下面这样:

 <map:mapDefine id="editParams" scope="session" type="hash"> 
...
</map:mapDefine>

这个标签将在会话范围内创建一个名为 editParamsHashMap

为了在标签处理程序中创建属性,需要定义相应的 JavaBean 属性。 因此,每一个属性在标签处理程序中都有对应的 setter 方法,如下所示:

 public class MapDefineTag extends TagSupport { 
...
private String type = FASTTREE;
private String id;
private String scope;


public void setType(String string)
{ type = string; }

public void setId(String string)
{ id = string; }
public void setScope(String string)
{ scope = string; }

转换引擎将用硬编码的配置数据或者运行时表达式设置这个标签的属性。 我们将在 第 4 步:在 TLD 文件中定义属性 中对此做更详细的讨论。

第 5 步:实现 doStartTag() 方法 中,我们将在标签处理程序的 doStartTag() 方法中使用这些属性。

第 4 步:在 TLD 文件中定义属性 第 6 页(共9 页)


就 像上一小节中所做的那样,通过声明 JavaBean 属性定义自定义属性,然后在 TLD 文件中声明这些属性。 每一个 JavaBean 属性都必须与相应的自定义标签属性相匹配。 在 TLD 中定义的属性必须匹配 JavaBean 属性,不过却可以有与标签属性不匹配的 JavaBean 属性。

下面是 MapDefineTag 的属性声明:

 <tag> <name>mapDefine</name> <tag-class>trivera.tags.map.MapDefineTag</tag-class> <body-content>JSP</body-content> ... <attribute> <name>id</name> <required>true</required> <rtexprvalue>false</rtexprvalue> <description>The id attribute</description> </attribute> <attribute> <name>scope</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>The scope attribute</description> </attribute> <attribute> <name>type</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description> Specifies the type of map valid values are fasttree, fasthash, hash, tree </description> </attribute> </tag>  

name 元素指定属性的名字。required 元素指定属性是否是必需的(默认值是 false)。rtexprvalue 元素表明属性是硬编码了转换时的值还是允许使用运行时 scriptlet 表达式。

记住,MapDefineTag 类必须为前面描述的每一个属性定义一个 JavaBean 属性,我们在 第 3 步:在标签处理程序 Java 类中创建属性 中完成这个任务。

第 5 步:实现 doStartTag() 方法 第 7 页(共9 页)


标签开始时调用 doStartTag() 方法 —— 从开发人员的角度看,这是当引擎遇到 <map:mapDefine> 时发生的。如果 doStartTag() 返回 SKIP_BODY,那么将不处理标签正文。 如果它返回一个 EVAL_BODY_INCLUDE,那么将处理正文。

MapDefine 类的 doStartTag() 方法完成以下工作:

  • 根据 type 属性确定要创建的 map 的属性。
  • 根据 scope 属性确定新的 map 对象放在什么范围内。
  • 根据 id 属性确定新 map 对象要放入的范围的名字。

让我们更详细地分析这个过程。MapDefine 类检查 type 属性是设置为 FASTTREEHASHTREE 还是 FASTHASH。然后创建相应的 map,如下所示:

 /* String constants for the different types of maps we support */ public static final String FASTHASH = "FASTHASH"; public static final String FASTTREE = "FASTTREE"; public static final String HASH = "HASH"; public static final String TREE = "TREE"; /** The map we are going to create */ private Map map = null; /** The member variable that holds the type attribute */ private String type = FASTTREE; ... public int doStartTag() throws JspException { /** Based on the type attribute, determines which type of Map to create */ if (type.equalsIgnoreCase(FASTTREE)) { map = new FastTreeMap(); } else if (type.equalsIgnoreCase(HASH)) { map = new HashMap(); } else if (type.equalsIgnoreCase(TREE)) { map = new TreeMap(); } else if (type.equalsIgnoreCase(FASTHASH)) { map = new FastHashMap(); }  

然后,用 idscope 属性将 hashmap 以一个给定的名字设置到一个给定范围中:

 private String id; private String scope; public int doStartTag() throws JspException { ... if (scope == null){ pageContext.setAttribute(id, map); }else if("page".equalsIgnoreCase(scope)){ pageContext.setAttribute(id, map); }else if("request".equalsIgnoreCase(scope)){ pageContext.getRequest().setAttribute(id, map); }else if("session".equalsIgnoreCase(scope)){ pageContext.getSession().setAttribute(id, map); }else if("application".equalsIgnoreCase(scope)){ pageContext.getServletContext().setAttribute(id, map); } return EVAL_BODY_INCLUDE; }  

如果范围属性是 null,那么 map 将放入页范围。否则,参数将放入通过 scope 属性传递的范围名所对应的范围中。

到目前为止,我们已经有一个非常简单的标签,它有三个属性:idscopetype。 我们将用给定的名字将 map 放到一个范围中。但是,我们还有一件事没做,就是声明 scriptlet 变量。 为了做到这一点,需要向 TLD 再添加一项,这就是我们在下一小节中所要做的事。


只有注册用户登录后才能发表评论。


网站导航: