《JSP自定义标签的开发与应用.doc》由会员分享,可在线阅读,更多相关《JSP自定义标签的开发与应用.doc(97页珍藏版)》请在三一办公上搜索。
1、JSP自定义标签的开发与应用在JSTL提供了四个标签库(核心标签库、国际化标签库、数据库标签库和XML标签库),涉及到了几十个标签。虽然这些标签可以完成比较复杂的工作,但它们仍然无法满足程序中的特殊需求。因此,就需要用户根据自己的需要来定制JSP标签,这种由用户自己实现的JSP标签被称为自定义标签。 .1 自定义标签基础 自定义标签和JSTL中的标签从技术上看没有任何区别,可以将这些标签统称为JSP标签。JSP标签在JSP页面中通过XML语法格式被调用,当JSP引擎将JSP页面翻译成Servlet时,就将这些调用转换成执行相应的Java代码。也就是说,JSP标签实际上就是调用了某些Java代码
2、,只是在JSP页面中以另外一种形式(XML语法格式)表现出来。 1.1 编写输出随机数的标签 开始自定义标签的学习之前,在这一节先来实现一个简单的自定义标签,以使读者做一下热身,同时读者通过本节的例子可以对自定义标签的实现方法和过程有一个感性的认识。 【实例1-1】 实现输出随机数的标签 1. 实例说明 在本例实现的自定义标签(random标签)的功能是输出一个指定范围的随机整数。random标签有如下几个特征: 没有标签体。 有两个属性:min和max。其中min属性表示生成随机数的最小值,max属性表示生成随机数的最大值。min属性的默认值是0,max属性的默认值是Integer.MAX_
3、VALUE。 生成随机数的范围是min = random max。 random标签的标准调用形式如下: 其中“ct”是调用标签时的前缀,通过taglib指定的prefix属性指定。上面的代码的功能是输出一个在1(包括)和100(不包括)之间的随机数。 2. 编写标签类 标签类是自定义标签的核心部分。实现标签类的方法有很多,但最简单的方法是编写一个从javax.servlet.jsp.tagext.TagSupport类继承的Java类,并在该类中覆盖TagSupport类的doStartTag方法。为了读取标签中的属性值,还需要在标签类中为每一个标签属性提供一个相应数据类型的标签类属性以及该
4、属性的setter方法(不需要getter方法)。生成随机数的代码需要放在标签类的doStartTag方法中。该标签类的实现代码如下: package chapter1; import java.io.IOException; import java.util.Random; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; public class RandomTag extends TagSupport / 封装random标签的两个属性的JavaBean属性 private
5、 int min = 0; private int max = Integer.MAX_VALUE; / min属性的setter方法 public void setMin(int min) this.min = min; / max属性的setter方法 public void setMax(int max) this.max = max; / 覆盖TagSupport类的doStartTag方法 / 当遇到标签(也就是)的开始标记时调用该方法 Override public int doStartTag() throws JspException try Random random = n
6、ew Random(); / 生成一个在min和max之间的随机数 int result = min + random.nextInt(max - min); / 将生成的随机数输出到客户端 pageContext.getOut().write(String.valueOf(result); catch (IOException e) / TagSupport类的doStartTag方法默认返回SKIP_BODY,表示忽略自定义标签体 return super.doStartTag(); 在RandomTag类的doStartTag方法中使用了一个pageContext变量来获得JspWrit
7、er对象(JSP的out内置对象)。pageContext变量是在TagSupport类中定义的一个类变量,该变量通过TagSupport类中的setPageContext方法进行赋值。实际上,setPageContext方法是在Tag接口中定义的,而TagSupport实现了Tag接口的setPageContext方法。Servlet容器在调用doStartTag方法之前,会先调用Tag接口的setPageContext方法来初始化pageContext变量。Tag接口将在1.1.3节介绍,在这里只要知道所有的标签类都必须实现Tag接口。为了简化标签类的实现,JSP API提供了一个TagS
8、upport类,有了TagSupport类,用户在编写标签类时就不需要实现Tag接口的所有方法了。 3. 编写标签库描述符文件(TLD文件) 在JSTL中有若干TLD文件(详见图9.1),其中tld是Tag Library Descriptor(标签库描述符)的缩写。要想使一个自定义标签正常工作,必须在TLD文件中对该标签进行配置。在TLD文件中主要配置如下两部分内容: 标签库的配置信息、标签库中具体标签的配置信息 标签库的配置信息主要包括如下的内容: 标签库的版本:使用标签设置。 正常使用标签库中的标签所需要的最低JSP版本:使用标签设置。 标签库的默认前缀:使用标签设置。 标签库的URI:
9、使用标签设置。 标签库的描述信息:使用标签设置。 标签的配置信息主要包括如下的内容: 标签名:使用标签设置。 标签对应的Java类:使用标签设置。 标签体的类型:使用标签设置。 标签的描述信息:使用标签设置。 标签属性的信息:每一个标签属性对应一个标签。在标签中可以指定标签名(使用标签设置)、该属性是否必须指定(使用标签设置)、该属性是否支持动态属性值(使用标签指定)等配置信息。 在WEB-INF目录中建立一个jsp-taglib.tld文件,并在该文件中输入如下的内容: 自定义标签库 1.1 ct 产生一个指定范围的随机数 random chapter1.RandomTag empty mi
10、n false false max false false 上面的配置代码分为三部分:标签库描述符文件头、标签库配置信息和random标签的配置信息。其中描述库文件头的内容并不需要读者去记忆,读者只需要将JSTL中的任何一个TLD文件打开,将其中的标签库描述文件头部分复制过来即可。如果在标签库描述符文件中包含中文,需要将encoding属性改成“UTF-8”或“GBK”。 标签库的URI被元素指定为“”,该标签值就是taglib指令的uri属性值。元素指定了标签库的默认前缀。要注意的是,该默认前缀并不等于taglib指令的prefix属性值,也就是说,taglib指令的prefix属性值和元素
11、的值毫无关系。元素值实际上只是个推荐的标签库前缀,如国际化标签库的描述符文件(fmt.tld)中的元素值是“fmt”。在使用taglib指令引用某个标签库时,应尽量使用元素推荐的标签库前缀,当然,也可以设置其他的前缀名。 random标签的标签体类型为“empty”(元素的值),表示该标签不支持标签体。关于元素支持的其他值,将在后面的部分详细介绍。random标签的min属性和max属性都被设置可选的属性,并且都不支持动态属性值。因此,在JSP页面中设置random标签的这两个属性时只能直接为它们赋值。 4. 测试random标签 在chapter1目录建立一个random.jsp文件(在本章
12、的所有JSP文件都放在该目录下),并输入如下的内容: 在浏览器地址栏中输入如下的URL: http:/localhost:8080/demo/chapter1/random.jsp 浏览器显示的输出结果如图1.1所示。 图1.1 使用random标签产生1个10至200之间的随机数 5. 程序总结 在部署和安装自定义标签时,TLD文件应放在WEB-INF目录或其子目录中(包括classes和lib目录)。根据本例实现的random标签,可以将开发自定义标签的基本步骤总结如下: 实现一个标签类。该标签类可以实现Tag接口、继承TagSupport类或实现其他的接口(这些接口将在后面的部分详细介绍
13、)。 在标签库描述符文件(TLD文件)中配置自定义标签。 部署和安装自定义标签。主要是将.class文件放在WEB-INFclasses目录中,并且将TLD文件放在WEB-INF目录或其子目录中。 1.1.2 自定义标签能做什么 自定义标签除了可以读取标签的属性值外,还可以完成如下的工作: 单次执行标签体中的内容、重复执行标签体中的内容。 修改标签体中的内容。 忽略JSP页面中位于自定义标签后面的内容。 在上一节介绍了Tag接口中的doStartTag方法,该方法在Web容器执行到自定义标签的开始标记时被调用。除了这个方法,在Tag接口中还有doEndTag方法,该方法在Web容器中执行到自定
14、义标签的结束标记时被调用。 doStartTag方法可以通过返回如下两个值来控制Web容器是否执行自定义标签的标签体: EVAL_BODY_INCLUDE:执行自定义标签的标签体。 SKIP_BODY:忽略(不执行)自定义标签的标签体。 doEndTag方法可以通过返回如下两个值来控制Web容器是否忽略JSP页面中位于自定义标签后面的内容: EVAL_PAGE:继续执行自定义标签后面的内容。 SKIP_PAGE:忽略自定义标签后面的内容。 其中EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE和SKIP_PAGE是在Tag接口中定义的整型常量,所有实现Tag接口的类都可
15、以直接使用这些常量。 除此之外,实现IterationTag接口的标签类还可以重复执行标签体。IterationTag是Tag接口的子接口。在IterationTag接口中有一个doAfterBody方法,该方法可以通过返回如下两个值来决定是否重复执行自定义标签体的内容: EVAL_BODY_AGAIN:重复执行标签体的内容。 SKIP_BODY:不再执行标签体的内容。 其中EVAL_BODY_AGAIN是在IterationTag接口中定义的整型常量。如果doAfterBody方法返回SKIP_BODY,Web容器会继续执行自定义标签的结束标记,同时会调用doEndTag方法。 从上面的描述
16、可以将Web容器执行自定义标签的过程总结如下: 1. Web容器首先会执行自定义标签的开始标记,同时会调用标签类的doStartTag方法。 2. 如果doStartTag方法返回EVAL_BODY_INCLUDE,Web容器在执行完标签体的内容后,会调用标签类的doAfterBody方法;如果doStartTag方法返回SKIP_BODY,doAfterBody方法不会被调用,Web容器会直接调用标签类的doEndTag方法。 3. 如果doAfterBody方法被调用,并且该方法返回EVAL_BODY_AGAIN,Web容器会再次执行标签体的内容;如果doAfterBody方法返回SKIP
17、_BODY,Web容器会调用标签类的doEndTag方法。 4. 如果doEndTag方法返回EVAL_PAGE,Web容器会执行自定义标签后面的内容;如果doEndTag方法返回SKIP_PAGE,Web容器会忽略自定义标签后面的内容。 1.1.3 自定义标签API 自定义标签API中除了前面介绍的Tag接口和IterationTag接口外,还有另外三个核心接口:JspTag、BodyTag和SimpleTag。为了简化自定义标签的编程工作,在自定义标签API中提供了TagSupport类、SimpleTagSupport类和BodyTagSupport类。其中TagSupport类实现了I
18、terationTag接口,SimpleTagSupport类实现了SimpleTag接口,BodyTagSupport类是TagSupport的子类,并实现了BodyTag接口。上述五个接口和三个类的关系如图1.2所示。 图1.2 自定义标签API的核心接口和类的关系图 上述的接口和类都在javax.servlet.jsp.tagext包中,读者可以从如下的网址来查询这些接口和类的详细介绍: 访问上面的网址将得到如图1.3所示的页面。 图1.3 自定义标签API的官方文档 上述五个接口的功能和作用如下: 1. JspTag接口 JspTag接口是所有自定义标签的父接口。它没有任何属性和方法。
19、Tag接口和SimpleTag接口是JspTag的两个直接子接口。其中JspTag和SimpleTag是JSP2.0新增的接口。在JSP2.0之前的版本的所有自定义标签的父接口是Tag。因此,可以将所有实现Tag接口的自定义标签称为传统标签,把所有实现SimpleTag接口的标签称为简单标签。在本章主要介绍传统标签,简单标签将在下一章详细介绍。 2. Tag接口 Tag接口是所有传统标签的父接口。该接口有两个核心方法(doStartTag和doEndTag)以及四个常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE和SKIP_PAGE)。其中doStartTag方
20、法可以返回EVAL_BODY_INCLUDE和SKIP_BODY,用于控制Web容器是否执行标签体的内容;doEndTag方法可以返回EVAL_PAGE和SKIP_PAGE,用于控制Web容器是否执行自定义标签后面的内容。 3. IterationTag接口 IterationTag接口继承了Tag接口。IterationTag接口可用于实现需要循环执行标签体内容的自定义标签。在IterationTag接口中只有一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。doAfterBody方法通过返回EVAL_BODY_AGAIN常量或Tag接口中的SKIP_BODY常量来控制
21、Web容器是否重复执行标签体中的内容。doStartTag方法、doAfterBody方法和doEndTag方法的调用关系详见1.1.2节中的介绍。 并不是每一个自定义标签都需要循环执行标签体的内容,或是控制自定义标签后面的内容是否被执行。因此,JSP API中提供了一个实现IterationTag接口的TagSupport类。在TagSupport类中对Tag接口和IterationTag接口中定义的方法都提供了默认的实现。如doStartTag方法、doEndTag方法和doAfterBody方法都提供了默认的返回值,代码如下: public class TagSupport implem
22、ents IterationTag, Serializable public TagSupport() public int doStartTag() throws JspException return SKIP_BODY; public int doEndTag() throws JspException return EVAL_PAGE; public int doAfterBody() throws JspException return SKIP_BODY; / 此处省略了TagSupport类中的其他方法和属性 . . 标签类通过继承TagSupport类,就不需要实现Tag接口和
23、Iteration接口中的每一个方法了,这样将大大简化自定义标签的开发工作。 4. BodyTag接口 BodyTag接口继承了IterationTag接口。BodyTag接口不仅拥有IterationTag接口的所有功能,而且还可以初始化和修改标签体的内容。在BodyTag接口中定义了两个方法(doInitBody和setBodyContent)和两个常量(EVAL_BODY_BUFFERED和EVAL_BODY_TAG),这两个常量的含义相同。其中EVAL_BODY_TAG常量是在JSP1.2中的遗留产物,在JSP的后续版本中可能不支持该常量,因此,建议使用EVAL_BODY_BUFFER
24、E常量。如果doStartTag方法返回EVAL_BODY_BUFFERE,Web容器就会将标签体的执行结果保存在BodyContent对象中,然后Web容器在处理标签时会调用标签类的setBodyContent方法将BodyContent对象传入标签类的对象实例,接下来就可以在标签类的对象实例中处理标签体的执行结果了。 由于BodyTag接口及其父接口中定义了很多方法,为了在实现BodyTag接口的类中不用再实现所有的方法,JSP API提供了一个BodyTagSupport类,该类是TagSupport类的子类,并且实现了BodyTag接口。在BodyTagSupport类中改变了doSt
25、artTag方法的默认返回值,并且覆盖了其他的核心方法,代码如下: public class BodyTagSupport extends TagSupport implements BodyTag protected BodyContent bodyContent; public BodyTagSupport() super(); public void setBodyContent(BodyContent b) this.bodyContent = b; public BodyContent getBodyContent() return bodyContent; / 改变了doStart
26、Tag方法的默认返回值 public int doStartTag() throws JspException return EVAL_BODY_BUFFERED; public int doEndTag() throws JspException return super.doEndTag(); public void doInitBody() throws JspException public int doAfterBody() throws JspException return SKIP_BODY; / 此处省略了BodyTagSupport类的其他方法和属性 . . 从前面的内容可
27、知,自定义标签API涉及到了三个方法(doStartTag、doAfterBody和doEndTag)以及这三个方法可能返回的六个常量(EVAL_BODY_INCLUDE、EVAL_BODY_BUFFERE、SKIP_BODY、EVAL_BODY_AGAIN、EVAL_PAGE和SKIP_PAGE)。表1.1给出了这三个方法的返回值和这六个常量的关系。 表1.1 doStartTag、doAfterBody和doEndTag方法的返回值及其作用 方法名 返回值 doStartTag doAfterBody doEndTag EVAL_BODY_INCLUDE 执行标签体中的内容(直接将标签体的
28、执行结果输出到out对象的缓冲区中) EVAL_BODY_BUFFERE 执行标签体中的内容,并将标签体的执行结果保存在BodyContent对象中,以备后续处理和加工 SKIP_BODY 忽略标签体的内容 不再重复执行标签体的内容 * EVAL_BODY_AGAIN * 重复执行标签体的内容 * EVAL_PAGE * * 继续执行自定义标签后面的内容 SKIP_PAGE * * 忽略自定义标签后面的内容 5. SimpleTag接口 SimpleTag是JSP2.0新增的一个接口。该接口只有一个doTag方法,这个方法只在Web容器执行自定义标签时调用一次。所有的处理逻辑(包括是否执行标签
29、体、重复执行标签体等)都要写在doTag方法中,因此,SimpleTag接口的功能相当于BodyTag接口,只是SimpleTag接口要比BodyTag接口更容易使用。为了更进一步简化自定义标签的开发工作,JSP API提供了一个实现SimpleTag接口的SimpleTagSupport类,建议读者在编写简单标签时,标签类从SimpleTagSupport类继承。 1.2 实现基本的自定义标签 一个自定义标签最基本的功能就是控制是否执行标签体的内容,以及控制是否执行自定义标签后面的内容。通过实现Tag接口的doStartTag方法和doEndTag方法可以很容易地完成这两个功能。在Tag接口
30、中还有定义了一些其他的方法(setPageContext、setParent等),通过实现这些方法,可以编写更高级的自定义标签。 1.2.1 Tag接口 javax.servlet.jsp.tagext.Tag接口是所有传统标签的父接口,该接口定义了Web容器处理自定义标签的基本方法。这些方法主要包括Web容器执行到标签的开始标记时发生的标签开始事件(调用Tag接口的doStartTag方法)和执行到标签的结束标记时发生的标签结束事件(调用Tag接口的doEndTag方法)。在Tag接口中定义了四个常量,并由doStartTag方法和doEndTag方法返回相应的常量以通知Web容器如何执行自
31、定义标签。Tag接口中定义的常量和方法如下所示: 1. Tag接口中定义的常量 在Tag接口中定义了如下四个整型常量: EVAL_BODY_INCLUDE SKIP_BODY EVAL_PAGE SKIP_PAGE 其中EVAL_BODY_INCLUDE和SKIP_BODY将作为doStartTag方法的返回值返回给Web容器, Web容器根据doStartTag方法的返回值来决定是否执行自定义标签的标签体。EVAL_PAGE和SKIP_PAGE将作为doEndTag方法的返回值返回给Web容器, Web容器根据doEndTag方法的返回值决定是否执行的自定义标签后面的内容。 2. setPa
32、geContext方法 在Web容器创建标签类的对象实例后,会首先调用标签类的setPageContext方法将PageContext对象实例传入标签类的对象实例,然后会调用标签类中的其他方法,这样在标签类中的其他方法就可以使用PageContext对象了。setPageContext方法的定义如下: public void setPageContext(PageContext pageContext) 3. setParent方法和getParent方法 Web容器在调用标签类的setPageContext方法后,会调用标签类的setParent方法,该方法用来设置当前标签的父标签的对象实例
33、。通过getParent标签可以获得当前标签的父标签的对象实例。如果当前标签没有父标签,setParent方法的参数值为null。setParent方法和getParent方法的定义如下: public void setParent(Tag t) public Tag getParent() 4. doStartTag方法 Web容器在依次调用setPageContext方法、setParent方法以及设置当前标签属性的setter方法后,就会调用标签类的doStartTag方法。当Web容器调用doStartTag方法时,就意味着Web容器已经开始处理当前标签的开始标记。如果doStartT
34、ag方法返回EVAL_BODY_INCLUDE,Web容器会在执行完标签体后调用标签类的doEndTag方法;如果doEndTag方法返回SKIP_BODY,Web容器并不会执行标签体,而是直接调用标签类的doEndTag方法。doStartTag方法的定义如下: int doStartTag() throws JspException 5. doEndTag方法 Web容器在遇到标签的结束标记时,会调用标签类的doEndTag方法。如果doEndTag方法返回EVAL_PAGE,Web容器会继续执行结束标记后面的内容;如果doEndTag方法返回SKIP_PAGE,结束标签后面的内容都不会被
35、执行,也就是说,Web容器在执行当前JSP页面时,执行到标签的结束标记处就会终止执行当前的JSP页面。doEndTag方法的定义如下: int doEndTag() throws JspException 6. release方法 JSP规范要求Web容器必须在垃圾回收器回收标签类的对象实例时调用release方法,以便自定义标签可以利用该方法释放所占用的相关资源。但JSP规范并没有规定Web容器调用release方法的具体时间,因此,release方法的调用时间由具体的Web容器厂商决定。release方法的定义如下: public void release() 1.2.2 标签类中方法的调
36、用顺序 在本节给出一个例子来测试标签类中方法的调用顺序。本示例测试了Web容器在调用自定义标签时调用标签类中的setPageContext方法、setParent方法、设置标签属性的setter方法、doStartTag方法、doEndTag方法和release方法的顺序。 【实例1-2】 测试标签类中方法的调用顺序 1. 编写InvokeOrderTag类 InvokeOrderTag是一个标签类,在该类中覆盖了TagSupport类中的相关方法,并在这些方法的调用轨迹输出到控制台。InvokeOrderTag类的实现代码如下: package chapter1; import javax.
37、servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.tagext.TagSupport; public class InvokeOrderTag extends TagSupport / 定义了两个属性,用于设置和读取标签的相应属性值 private String attr1; private String attr2; / 设置attr1属性的值 public void setAttr1(Str
38、ing attr1) System.out.println(setAttr1); this.attr1 = attr1; / 设置attr2属性的值 public void setAttr2(String attr2) System.out.println(setAttr2); this.attr2 = attr2; Override public void setPageContext(PageContext pageContext) System.out.println(pageContext); super.setPageContext(pageContext); Override pu
39、blic void setParent(Tag t) System.out.println(setParent); System.out.print(父标签:); System.out.println(t); super.setParent(t); public int doStartTag() throws JspException System.out.println(doStartTag); return super.doStartTag(); public int doEndTag() throws JspException System.out.println(doEndTag);
40、return super.doEndTag(); Override public void release() System.out.println(release); super.release(); 2. 安装invokeOrder标签 在jsp-taglib.tld文件中添加如下的内容来安装invokeOrder标签: 测试标签类中方法的调用顺序 invokeOrder chapter1.InvokeOrderTag empty attr1 false false attr2 false false 3. 编写invokeorder.jsp页面 invokeorder.jsp页面用来调用invokeOrder标签。当访问invokeorder.jsp页面后,就会在Tomcat控制台输出相应的方法调用轨迹。invokeorder.jsp页面的代码如下: % page language=java cont