开发定制标签库

tld文件是对标签库的说明。一个tld文件就是一个以xml格式书写的文本文件。
因此,熟悉tld文件就是熟悉tld文件中的标签。例如:

<?xml version="1.0" encoding="ISO-8859-1">

<!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>test</short-name>
 <uri>http://www.manning.com/sampleLib</uri>

<tag>
 <name>greet</name>
 <tag-class>sampleLib.GreetTag</tag-class>
 <body-content>empty</body-content>
 <description>Prints Hello and the user name</description>

<attribute>
 <name>user</name>
 <required>false</required>
 <rtexprvalue>true</rtexprvalue>
</attribute>

</tag>

</taglib>
上例清晰的向使用者表达了三件事。
1.象所有XML文件一样,第一行标明了XML的版本号及字符编码;
2.接下来的DOCTYPE声明了文档类型。对于JSP1.2版本,必须按上例书写;
3.最后,由<taglib>所包围的才是内容的主体。我们所要熟悉的也就是这一部分。
下面来看看其中的几个重要元素:

<taglib>元素
是tld文件的顶层元素,根元素。其下有很多二级元素,如下表:


元素  描述  出现次数 
tlib-version  标签库版本  只允许一次 
jsp-version  指定所遵循的jsp规范的版本  只允许一次 
short-name  作用同前缀  只允许一次 
uri  标识本库。作用同taglib-uri和taglib-location  多数情况就一次 
display-name  显示名称  多数情况就一次
samll-icon  作用同字面意义  多数情况就一次 
large-icon  作用同字面意义  多数情况就一次 
description  对本库的文字描述  多数情况就一次 
validator  多数情况就一次 
listener  指定事件侦听器类  任何数量 
tag  一个具体的标签对应一个tag,具体内容由tag子元素指定  至少出现一次 

   
注意其中的uri子元素是一种隐含标识标签库。不过,如果web.xml文件中显式的做了taglib-uri和taglib-location声明,则以web.xml为准。
我的理解是,tld文件中的uri属于开发时描述,web.xml是属于部署时描述。tld的主要职责是描述标签库,而具体标签库被放在哪里当然应该由web.xml文件描述。所以tld中的uri只能属于参考。

<tag>元素
tag用于具体描述每个标签。它和标签是一对一关系,有多少标签就会有多少tag。其下有很多二级元素来详细描述一个标签。见下表:


元素  描述  出现次数 
name  标签名  只允许一次 
tag-class  该标签实际对应的类  只允许一次 
tei-class  一个可选的附加类  多数情况就一次
body-content  标签体类型.empty,jsp,or tagdependt.缺省jsp  多数情况就一次 
display-name  标签显示名  多数情况就一次 
samll-icon  作用同字面意义  多数情况就一次 
large-icon  作用同字面意义  多数情况就一次 
description  对本标签的文字描述  多数情况就一次 
variable  指定变量信息  任何数量 
attribute  描述标签可接受的参数  任何数量 
example  标签使用示例   多数情况就一次 

对于同一个类定义不同的标签名是允许的。但是不允许有相同的标签名。

<attribute>元素
是<tag>的子元素。如果该标签可以接收外部参数,则需要用<attribute>来指定。一个参数一个<attribute>。
每个<attribute>又另外有5个子元素,用以描述可接收的外部参数,见下表:


元素  描述 
name  参数名(外部参数名) 
required  指定是否为必须。缺省是false。取值范围是true,false,yes,or no.
rtexprvalue  是否可以是请求时表达式。缺省是false。取值范围是true,false,yes,or no.
type  只有当<rtexprvalue>为true时才有效。指示请求时表达式的返回值类型。缺省是字符串。 
descrption  参数描述

以上元素只有name,是必须的,其它为可选。

<body-content>元素
该元素也是<tag>的子元素。这个元素只有三个值。empty,jsp,tagdependent。
.empty-指定该标签不可以有其它内容。
.jsp-指出该标签可以带任何可以正常转换成jsp代码的内容。如:文本,HTML标记,脚本元素,标准动作,甚至是另外的标签。地线是只要能正常转换成JAVA代码。
.tagdependent-指出其内容不是jsp代码。这样jsp引擎就不会去执行代码,转而交给其标签所对的class类去处理。通常出现这种值的情况是,所写代码不是java语言。例如:
<test:dbQuery>
SELECT count(*) FROM USERS
</test:dbQuery>
显然其中的内容是SQL语句。

由于tld文件本身是以XML格式书写的,所以要遵循以下规则。
1.元素间的层次和顺序关系要注意。
2.大小写敏感。
3.对于jsp 1.2版本,必须带连字符-。象<tagclass>在jsp 1.1中是合法的,但在1.2版必须写成<tag-class>。


标签扩展API
标签扩展API是一组接口和类。我们写servlet需要用到servlet API,为了写定制标签我们就需要标签扩展API(Tag Extension API)。
这个API只有一个包javax.servlet.jsp.tagext。其中有4个接口和13个类。主要的接口和类见下表:


接口  描述 
Tag 

所有的标签处理器都直接或间接的继承该接口。是该包的基接口。
共声明了6个方法,最重要的两个是doStartTag()和doEndTag()。
这个接口被用于简单标签的实现。这种简单标签没有重复执行部分,不需要处理标签体。 

IterationTag  继承了Tag接口,增加一个方法doAfterBody()。支持某部分的重复执行。 
BodyTag  继承IterationTag接口,增加两个方法doInitBody()和setBodyContent()。
支持对标签体的缓冲。 


类  描述 
TagSupport  实现了IterationTag接口,并对所有方法提供缺省实现。 
BodyTagSupport  实现了BodyTag接口,并对所有方法提供缺省实现。 
BodyContent  继承JspWriter类,用于临时为标签体的计算提供缓冲区。
这个对象只可以和BodyTag接口及BodyTagSupport类连用。 

   
此外标签处理器还使用另外两个在javax.servlet.jsp中定义的异常。


异常类  描述 
JspException  来源于java.lang.Exception异常。一些重要的方法如doStartTag(), doInitBody(),
doAfterBody(), and doEndTag()都会抛出JspException异常。
不捕捉这个异常将会触发error-page。 
JspTagException   扩展于JspException。标签处理器用它来指示不可恢复的错误。 

主要接口和类的继承关系如图:椭圆标识接口,矩形标识类。


在继续详述标签接口及类之前,先回忆一下定制标签的总体的调用机制。见图:(其中蓝色为声明过程,红色为调用过程)

实现Tag接口
所有的标签处理器都需要间接或直接的实现这个接口。
下面列出Tag接口定义的方法和常量:


方法名  描述 
int doStartTag()   当碰到标签起始标记时执行。(<XXX:XXX>) 
int doEndTag()  当碰到标签结束标记时执行。(</XXX:XXX>) 
Tag getParent()  获得一个父标签对象 
void release()  从内存中释放该标签对象 
void setPageContext(PageContext)  设置当前页上下文(page context) 
void setParent(Tag)  对父标签对象进行设置 


常量  描述 
EVAL_BODY_INCLUDE  doStartTag()的返回值。指出jsp引擎计算标记体并输出
SKIP_BODY  doStartTag()的返回值。指出jsp引擎跳过标记体且不输出 
EVAL_PAGE  doEndTag()的返回值。指出jsp引擎计算页面剩余部分并输出 
SKIP_PAGE  doEndTag()的返回值。指出jsp引擎跳过页面剩余部分且不输出 


setPageContext()方法
setPageContext()方法是一个定制标签声明周期内第一个要被调用的方法。完整写法是:
public void setPageContext(PageContext);
jsp引擎会将jsp页转换时隐含创建的pageContext对象,作为参数,调用setPageContext方法。
通常的做法会将这个参数保存为本标记处理器的参数。

setParent()和getParent()方法
当标签嵌套使用时,外层的标签被成为父标签,内部的被称为子标签。完整的写法是:
public void setParent(Tag);
public Tag getParent();
其中setParent方法对父标签对象进行设置。而getParent方法用以获得父标签对象。

setter方法
当定制标签中包含属性时,在标签处理器中有和javaBean相同的设置机制。即每一个属性XXX,都有对应的setXXX()方法。
这些方法的调用是在setPageContext()和setParent()之后,doStartTag()之前。(就是说所有的属性-即实例变量-必须完全赋值,因为随后的方法可能会调用到它们)

doStartTag()方法
该方法标志着真正的标签处理开始。当前三阶段的方法执行完后,jsp引擎就会调用doStartTag()方法了。完整写法是:
public int doStartTag() throws JspException;
该方法给了标签处理器一个初始化计算和校验属性值合法性的机会。如果初始化失败或属性值不合法,则抛出JspException异常,或其子类,如JspTagException。
初始化计算过后,该方法将决定是否继续对标签体进行计算。作为结果,返回一个整数常量(EVAL_BODY_INCLUDE或SKIP_BODY),用以指示是否跳过标签体的执行。除此之外不返回任何其它值。

doEndTag()方法
执行完doStartTag()方法后,就该执行doEndTag()了。完整写法是:
public int doEndTag() throws JspException;
该方法标志着真正的标签处理结束。同样该方法将决定是否继续执行jsp文件中该标签之后的内容。它也会返回一个整数常量(EVAL_PAGE或SKIP_PAGE),用以指示页面后续内容是否跳过。注意此处的范围是页,并不是转换单位。所以计算不计算全都是针对该页而言。

release()方法
最后,jsp引擎将调用release方法,当标签处理结束,标签处理器对象不再使用时。完整写法是:
public void release();
注意。一个页面内同时有多个相同标签时,jsp引擎会只会为该标签创建一个对象。只有等该页内相同标签全部处理完了。jsp引擎才会调用release()方法,将该标签对象从内存清理掉。

各方法的调用流程如下图:


实现IterationTag接口
IterationTag接口继承了Tag接口,允许对标签体做多次循环计算,就像编程语言中的循环。
在Tag的基础上,InterationTag增加了一个方法和一个常量。doAfterBody()和EVAL_BODY_AGAIN。

doAfterBody方法
在该方法中,doStartTag被调用之后会返回一个常量(EVAL_BODY_INCLUDE或SKIP_BODY)指出标签体是否被包括。
jsp引擎在对标签体进行了第一次计算后,会自动调用doAfterBody方法(第一次);在doAfterBody方法中会决定是否需要再次计算标签体。
当不需要再次计算时,该方法返回SKIP_BODY常量,并结束该方法的执行。
反之,该方法返回EVAL_BODY_AGAIN常量。jsp引擎在得到这个常量后,会对标签体做再次计算。接着再调用doAfterBody方法(第二次),以询问是否还要计算标签体。
周而复始,直至返回SKIP_BODY。
显然这时一个先执行后循环的循环语句,类似于java的do...while()语句。

jsp引擎自动调用doAfterTag的根据是来自于标签的实现是否是一个IterationTag实例,而不是根据tld文件的描述。

其流程图如下:


实现BodyTag接口
BodyTag接口继承了IterationTag接口。它提供了这样一种功能,将标签体的处理结果放到一个缓冲区,在缓冲区内对结果在进行处理,而不是象Tag或IterationTag接口将标签体的处理结果直接发送到输出流。
另外,由于继承了IterationTag接口,对标签体结果的处理也可以循环进行多次。

在该接口中定义了两个方法(setBodyContent()和doInitBody())和一个整数常量(EVAL_BODY_BUFFERED)。

setBodyContent()方法
完整写法是:
public int setBodyContent(BodyContent);
该方法的典型实现是将BodyContent赋值给本地局部变量以供后面使用。

doInitBody()方法
jsp引擎在调用完setBodyContent方法后会接着调用doInitBody方法。完整写法是:
public void doInitBody() throws JspException;

该接口的运行机制象前两种接口一样,先依次调用setPagerContext(),setParent(),实例变量的setXXX方法,doStartTag()。
这是doStartTag方法返回一个常量(EVAL_BODY_INCLUDE, SKIP_BODY或EVAL_BODY_BUFFERED)。
如果返回的是EVAL_BODY_BUFFERED,jsp引擎首先创建一个BodyContent类对象。BodyContent类是JspWriter的子类,而且所有的write,print方法都被重写。不再直接输出到客户端,而是输出到该对象(缓冲对象)内部。
jsp引擎接着将该对象作为参数带入到setBodyContent方法中,在执行完setBodyContent方法后,接着再调用doInitBody方法,此时并没有对缓冲对象进行计算,只是给出一个机会,对缓冲对象初始化。
接着,jsp引擎处理缓冲对象,并将结果再存在缓冲对象中。
再后来,doAfterBody方法被调用,它将返回EVAL_BODY_AGAIN或EVAL_BODY_BUFFERED用以指示缓冲对象需要循环计算,而返回SKIP_BODY则标明就此结束。
最后,呼叫doEndTag方法,结束标签处理。

其流程图如下:


继承TagSupport和BodyTagSupport类
前面我们依次介绍了Tag,IterationTag,BodyTag接口。实际应用当中,我们并不需要直接实现他们。jsp规范中已经为我们提供了两个类TagSupport和BodyTagSupport,用以对IterationTag和BodyTag的缺省实现。
所以,我们只需要根据需要去继承这两个类,然后重写我们需要定制的方法就可以了。

TagSupport类
该类是IterationTag的缺省实现。除了实现原有方法外,本身还增加了一些有用的其他方法和成员变量。下表列出其中重要的几个:


名称  描述 
重写的方法和他们的返回值 
void doStartTag()  继承自Tag。返回值SKIP_BODY。 
void doAfterBody()  继承自IterationTag。返回值SKIP_BODY。 
void doEndTag()  继承自Tag。返回值EVAL_PAGE。 
处理嵌套标签的有用的方法 
void setParent(Tag)  接受和维护一个父标签的引用。 
Tag getParent() 返回父标签的引用。 
Tag findAncestorWithClass(Tag,Class)  静态方法。寻找最近的指定类的标签。 
标签属性操作 
void setValue(String,Object)  设置属性。属性名是字符串,值可以是任何对象。 
Object getValue(String)  返回给定属性名的属性的值。 
Enumeration getValues()  返回一个所有值的枚举。 
void removeValue(String)  删除给定的属性。 
受保护的实例变量 
PageContext pageContext  一个保存着PageContext对象的变量。 

 
BodyTagSupport类
该类同时继承了TagSupport类,并实现BodyTag接口。除了前表所示方法,该类还提供了一些其它方法便于使用。


名称  描述 
重写的方法和他们的返回值 
void doStartTag()  继承自Tag。返回值EVAL_BODY_BUFFERED。 
void doAfterBody()  继承自IterationTag。返回值SKIP_BODY。 
void doEndTag()  继承自Tag。返回值EVAL_PAGE。 
用以处理缓冲的方法 
void setBodyContent(BodyContent)  接受和维护一个BodyContent对象(缓冲对象)的引用。 
BodyContent getBodyContent()  返回BodyContent对象的引用。 
JspWriter getPreviousOut()  返回JspWriter对象,BodyContent就是对他的简单包裹。 

      
定制标签和JavaBeans之间的简单区分
虽然两者都是为了提高组件可重用性而设计的。但是一个经常被问起的问题是:如何决定在何时使用其中的一种技术,而不是另一种。
这里有个简单的比较,来帮助你如何决策。
.JavaBeans是jsp页的数据处理器并增加数据管理的逻辑。主要用以存储。定制标签,倾向于对特定请求的处理。
.标签是线程安全的,而bean则不是。Bean的线程安全是由开发人员手工实现的。
.标签了解他所运行的外部环境(pageContext),而bean则不。
.标签属于转换单位的一部分,而bean独立于转换单位之外。
.标签可以访问隐含对象,bean则不能。(这是对上两条的细节描述)
.标签只有页范围(page scope)。他们在一个请求内和一个页面内被创建。虽然他们可以访问所有的范围。beans则可以被创建于各种不同的范围。标签可以创建和维护bean,反之不然。
.标签的API是围绕jsp页的概念开发的,所以标签只能用在jsp页中。而bean却可以被更广泛的组件使用。
.标签不是持久化对象。而bean可以被持久化(序列化),以被后续使用。

相比之下标签较靠近用户,且只能用于jsp页面;而bean较靠近商业逻辑,更适合处理数据处理和维护类工作,且可以被任何组件重用。因此,首选的使用方法是用户的请求由标签负责处理,而需要应用商业逻辑和数据处理的时候,由标签再调用bean来处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值