user>称为子标签。
为了便于组织和管理标签,可以把一组功能相关的标签放在同一个标签库中。
开发包含自定义标签的标签库包括以下步骤:
(1)创建自定义标签的处理类(TagHandlerClass)。
(2)创建TLD标签库描述文件(TagLibraryDescriptor)。
假定甲方开发了重用性比较高的标签库,那么除了甲方本身的Web应用可以使用它,其他方(如乙方)也可以使用它。
JSTLCore标签库的使用介绍了如何在Web应用中使用由第三方提供的JSP标准标签库(JSTL)。
本次课将按照如下步骤在Web应用中使用标签库:
(1)把标签处理类及相关类的class文件存放在WEB-INF/classes目录下。
(2)把TLD标签库描述文件存放在WEB-INF目录或者其自定义的子目录下。
(3)在web.xml文件中声明所引用的标签库。
(4)在JSP文件中使用标签库中的标签。
2JSPTagAPI
Servlet容器在运行JSP文件时,如果遇到自定义标签,就会调用这个标签的处理类(TagHandlerClass)的相关方法。
标签处理类可以继承JSPTagAPI中的TagSupport类或者BodyTagSupport类。
JSPTagAPI位于javax.servlet.jsp.tagext包中,图13-1是其中的主要接口和类的类框图。
图13-1JSPTagAPI
2.1JspTag接口
所有的标签处理类都要实现JspTag接口。
这个接口只是一个标识接口,没有任何方法,主要是作为Tag和SimpleTag接口的共同接口。
在JSP2.0以前,所有的标签处理类都要实现Tag接口,实现该接口的标签称为传统标签(ClassicTag)。
JSP2.0提供了SimpleTag接口,实现该接口的标签称为简单标签(SimpleTag)。
本章将介绍传统标签的用法。
2.2Tag接口
Tag接口定义了所有传统标签处理类都要实现的基本方法,包括以下几种。
●setPageContext(PageContextpc):
由Servlet容器调用该方法,向当前标签处理对象(即Tag对象)传递当前的PageContext对象。
●setParent(Tagt):
由Servlet容器调用该方法,向当前Tag对象传递父标签的Tag对象。
●getParent():
返回Tag类型的父标签的Tag对象。
●release():
当Servlet容器需要释放Tag对象占用的资源时,会调用此方法。
●doStartTag():
当Servlet容器遇到标签的起始标志时,会调用此方法。
doStartTag()方法返回一个整数值,用来决定程序的后续流程。
它有两个可选值,即Tag.SKIP_BODY和Tag.EVAL_BODY_INCLUDE。
Tag.SKIP_BODY表示标签之间的主体内容被忽略,Tag.EVAL_BODY_INCLUDE表示标签之间的主体内容被正常执行。
例如对于以下代码:
mytag>
HelloWorld
……
mytag>
假若标签的处理对象的doStartTag()方法返回Tag.SKIP_BODY,那么“HelloWorld”字符串不会显示在网页上;若返回Tag.EVAL_BODY_INCLUDE,那么“HelloWorld”字符串将显示在网页上。
●doEndTag():
当Servlet容器遇到标签的结束标志时,就会调用doEndTag()方法。
doEndTag()方法也返回一个整数值,用来决定程序后续流程。
它有两个可选值,即Tag.SKIP_PAGE和Tag.EVAL_PAGE。
Tag.SKIP_PAGE表示立刻停止执行标签后面的JSP代码,网页上未处理的静态内容和Java程序片段均被忽略,任何已有的输出内容立刻返回到客户的浏览器上。
Tag.EVAL_PAGE表示按正常的流程继续执行JSP文件按。
以上提到的Tag.SKIP_BODY、Tag.EVAL_BODY_INCLUDE、Tag.SKIP_PAGE和Tag.EVAL_PAGE是在Tag接口中定义的4个int类型的静态常量,用于指示标签处理流程。
标签处理类的对象(Tag对象)由Servlet容器负责创建。
Servlet容器在执行JSP文件时,如果遇到JSP文件中的自定义标签,就会寻找缓存中的相关的Tag对象,如果还不存在,就创建一个Tag对象,把它放在缓存中,以便下次处理自定义标签时重复使用。
当Servlet容器得到了Tag对象后,会按照如图13-2所示的流程调用Tag对象的相关方法:
(1)Servlet容器调用Tag对象的setPageContext()和setParent()方法,把当前JSP页面的PageContext对象及父标签处理对象传给当前Tag对象。
如果不存在父标签,则把父标签处理对象设为null。
(2)Servlet容器调用Tag对象的一系列set方法,设置Tag对象的属性。
如果标签没有属性,则无需这个步骤。
(3)Servlet容器调用Tag对象的doStartTag()方法。
(4)如果doStartTag()方法返回Tag.SKIP_BODY,就不执行标签主体的内容;如果doStartTag()方法返回Tag.EVAL_BODY_INCLUDE,就执行标签主体的内容。
(5)Servlet容器调用Tag对象的doEndTag()方法。
(6)如果doEndTag()方法返回Tag.SKIP_PAGE,就不执行标签后续的JSP代码;如果doEndTag()方法返回Tag.EVAL_PAGE,就执行标签后续的JSP代码。
图13-2Servlet容器调用Tag对象的相关方法的流程
注:
一个Tag对象在被创建后,就会一直存在,可以被Servlet容器重复调用。
当Web应用终止时,Serlvet容器会先调用该Web应用中的所有Tag对象的release()方法,然后销毁这些Tag对象。
2.3IterationTag接口
IterationTag接口继承自Tag接口,IterationTag接口增加了重复执行标签主体内容的功能。
IterationTag接口定义了一个doAfterBody()方法,Servlet容器在执行完标签主体内容后,会调用此方法。
如果Serlvet容器未执行标签主体内容,那么就不会调用此方法。
doAfterBody()方法也返回一个整数值,用来决定程序后续流程,它有两个可选值:
Tag.SKIP_BODY和IterationTag.EVAL_BODY_AGAIN。
Tag.SKIP_BODY表示不再执行标签主体内容;IterationTag.EVAL_BODY_AGAIN表示重复执行标签主体内容。
IterationTag接口还定义了一个可作为doAfterBody()方法的返回值得int类型的静态常量:
IterationTag.EVAL_BODY_AGAIN,用于指示Servlet容器重复执行标签主体内容。
Servlet容器在处理JSP文件中的这种标签时,会寻找缓存中的相关的IterationTag对象,如果还不存在,就创建一个IterationTag对象,把它放在缓存中,以便下次处理自定义标签时重复使用。
Servlet容器在得到了IterationTag对象后,会按照如图13-3所示的流程调用IterationTag对象的相关方法:
(1)Servlet容器调用IterationTag对象的setPageContext()和setParent()方法,把当前JSP页面的PageContext对象及父标签处理对象传给当前IterationTag对象。
如果不存在父标签,则把父标签处理对象设为null。
(2)Servlet容器调用IterationTag对象的一系列set方法,设置Tag对象的属性。
如果标签没有属性,则无需这个步骤。
(3)Servlet容器调用IterationTag对象的doStartTag()方法。
(4)如果doStartTag()方法返回Tag.SKIP_BODY,就不执行标签主体的内容;如果doStartTag()方法返回Tag.EVAL_BODY_INCLUDE,就执行标签主体的内容。
(5)如果在步骤(4)中Servlet容器执行了标签主体的内容,那么就调用doAfterBody()方法。
(6)如果doAfterBody()方法返回Tag.SKIP_BODY,就不再执行标签主体的内容;如果doAfterBody()方法返回IterationTag.EVAL_BODY_AGAIN,就继续重复执行标签主体的内容。
(7)Servlet容器调用IterationTag对象的doEndTag()方法。
(8)如果doEndTag()方法返回Tag.SKIP_PAGE,就不执行标签后续的JSP代码;如果doEndTag()方法返回Tag.EVAL_PAGE,就执行标签后续的JSP代码。
与Servlet容器调用Tag对象的流程相比,可以看出,以上步骤(5)和步骤(6)是增加的步骤。
图13-3Servlet容器调用IterationTag对象的相关方法的流程
2.4BodyTag接口
BodyTag接口继承自IterationTag接口,增加了直接访问和操纵标签主体内容的功能。
BodyTag接口定义了两个方法。
●setBodyContent(BodyContentbc):
Servlet容器通过此方法向BodyTag对象传递一个用于缓存标签主体的执行结果的BodyConent对象。
●doInitBody():
当Servlet容器调用完setBodyContent()方法之后,在第一次执行标签主体之前,先调用此方法,该方法用于为执行标签主体做初始化工作。
只要符合以下两种条件之一,setBodyContent(BodyContentbc)和doInitBody()就不会被Servlet容器调用:
●标签主体为空。
●doStartTag()方法的返回值为Tag.SKIP_BODY或者Tag.EVAL_BODY_INCLUDE。
只有同时符合以下两个条件,Servlet容器才会调用setBodyContent(BodyContentbc)和doInitBody()方法:
●标签主体不为空。
●doStartTag()方法的返回值为BodyTag.EVAL_BODY_BUFFERED。
以上提到的BodyTag.EVAL_BODY_BUFFERED是在BodyTag接口中定义的int类型的静态常量,它可作为doStartTag()方法的返回值,指示Servlet容器调用BodyTag对象的setBodyContent()和doInitBody()方法。
Servlet容器在处理JSP文件中的这种标签时,会寻找缓存中的相关的BodyTag对象,如果还不存在,就创建一个BodyTag对象,把它放在缓存中,以便下次处理自定义标签时重复使用。
Servlet容器在得到了BodyTag对象后,会按照如图13-4所示的流程调用BodyTag对象的相关方法:
(1)Servlet容器调用BodyTag对象的setPageContext()和setParent()方法,把当前JSP页面的PageContext对象及父标签处理对象传给当前BodyTag对象。
如果不存在父标签,则把父标签处理对象设为null。
(2)Servlet容器调用BodyTag对象的一系列set方法,设置Tag对象的属性。
如果标签没有属性,则无需这个步骤。
(3)Servlet容器调用BodyTag对象的doStartTag()方法。
(4)如果doStartTag()方法返回Tag.SKIP_BODY,就不执行标签主体的内容;如果doStartTag()方法返回Tag.EVAL_BODY_INCLUDE,就执行标签主体的内容;如果doStartTag()方法返回BodyTag.EVAL_BODY_BUFFERED,就先调用setBodyContent()和doInitBody()方法,再执行标签主体的内容。
(5)如果在步骤(4)中Servlet容器执行了标签主体的内容,那么就调用doAfterBody()方法。
(6)如果doAfterBody()方法返回Tag.SKIP_BODY,就不再执行标签主体的内容;如果doAfterBody()方法返回IterationTag.EVAL_BODY_AGAIN,就继续重复执行标签主体的内容。
(7)Servlet容器调用BodyTag对象的doEndTag()方法。
(8)如果doEndTag()方法返回Tag.SKIP_PAGE,就不执行标签后续的JSP代码;如果doEndTag()方法返回Tag.EVAL_PAGE,就执行标签后续的JSP代码。
图13-4Servlet容器调用BodyTag对象的相关方法的流程
2.5TagSupport类和BodyTagSupport类
TagSupport类和BodyTagSupport类是标签实现类,其中TagSupport类实现了IterationTag接口,而BodyTagSupport类则继承自TagSupport类,并且实现了BodyTag接口。
用户自定义的标签处理类可以继承TagSupport类或者BodyTagSupport类。
例程13-1是TagSupport类的部分源代码。
例程13-1TagSupport.java
importjava.io.Serializable;
importjava.util.Enumeration;
importjava.util.Hashtable;
importjavax.servlet.jsp.JspException;
importjavax.servlet.jsp.PageContext;
importjavax.servlet.jsp.tagext.IterationTag;
importjavax.servlet.jsp.tagext.Tag;
publicclassTagSupportimplementsIterationTag,Serializable{
publicstaticfinalTagfindAncestorWithClass(Tagfrom,
@SuppressWarnings("unchecked")Classklass){
booleanisInterface=false;
if(from==null
||klass==null
||(!
Tag.class.isAssignableFrom(klass)&&!
(isInterface=klass
.isInterface()))){
returnnull;
}
for(;;){
Tagtag=from.getParent();
if(tag==null){
returnnull;
}
if((isInterface&&klass.isInstance(tag))
||klass.isAssignableFrom(tag.getClass()))
returntag;
else
from=tag;
}
}
publicTagSupport(){
}
publicintdoStartTag()throwsJspException{
returnSKIP_BODY;
}
publicintdoEndTag()throwsJspException{
returnEVAL_PAGE;
}
publicintdoAfterBody()throwsJspException{
returnSKIP_BODY;
}
publicvoidrelease(){
parent=null;
id=null;
if(values!
=null){
values.clear();
}
values=null;
}
publicvoidsetParent(Tagt){
parent=t;
}
publicTaggetParent(){
returnparent;
}
publicvoidsetId(Stringid){
this.id=id;
}
publicStringgetId(){
returnid;
}
publicvoidsetPageContext(PageContextpageContext){
this.pageContext=pageContext;
}
publicvoidsetValue(Stringk,Objecto){
if(values==null){
values=newHashtable();
}
values.put(k,o);
}
publicObjectgetValue(Stringk){
if(values==null){
returnnull;
}else{
returnvalues.get(k);
}
}
publicvoidremoveValue(Stringk){
if(values!
=null){
values.remove(k);
}
}
publicEnumerationgetValues(){
if(values==null){
returnnull;
}
returnvalues.keys();
}
privateTagparent;
privateHashtablevalues;
protectedStringid;
protectedPageContextpageContext;
}
TagSupport类的主要方法参见表13-1。
表13-1TagSupport类的主要方法
方法
属性
doStartTag()
当Servlet容器遇到自定义标签的起始标志时调用该方法
doEndTag()
当Servlet容器遇到自定义标签的结束标志时调用该方法
setValue(Stringkey,Objectvalue)
在标签处理对象中设置key/value
getValue(Stringkey)
根据参数key返回匹配的value
removeValue(Stringkey)
在标签处理对象中删除key/value
setPageContext(PageContextpc)
设置PageContext对象,该方法由Servlet容器在调用doStartTag()方法前调用
setParent(Tagt)
设置父标签的处理对象,该方法由Servlet容器在调用doStartTag()方法前调用
getParent()
返回父标签的处理对象
1、parent和pageContext成员变量
TagSupport类有两个重要的成员变量。
●parent:
private访问级别的成员变量,代表父标签的处理对象。
●pageContext:
protected访问级别的成员变量,代表当前JSP页面的PageContext对象。
Servlet容器在调用doStartTag()方法前,会先调用setPageContext()和setParent()方法,设置pageContext和parent成员变量,因此在TagSupport子类的doStartTag()或doEndTag()等方法中可以通过getParent()方法获取父标签的处理对象。
在TagSupport类中定义了protected访问级别的pageContext成员变量,因此在TagSupport子类的doStartTag()或doEndTag()等方法中可以直接访问pageContext变量。
JSP课程中(PageContext抽象类)已经介绍了PageContext类的作用,它在标签处理类中将大有用武之地。
在本章及下一章的标签处理类的例子中,都会用到PageContext类。
注:
在TagSupport的构造方法中不能访问pageContext成员变量,因为此时Servlet容器还没有调用setPageContext()方法对pageContext成员变量进行初始化。
2、处理标签的方法
对于用户自定义的标签处理类,主要重新实现TagSupport类中的以下方法。
●doStartTag():
提供Servlet容器在遇到标签起始标志时执行的操作。
●doEndTag():
提供Servlet容器在遇到标签结束标志时执行的操作。
如果希望Servlet容器重复执行标签主体的内容,那么还可以重新实现TagSupport类的doAfterBody()方法。
3、用户自定义的标签属性
如果在标签中包含自定义的属性,例如:
mytagusername