自定义标签必须实现下面三个接口中的一个:
Tag
、
IterationTag
、
BodyTag
1.Tag
如果要实现这个接口,可以通过扩展 TagSupport 这个类,来写自己需要的方法,而不需要把 Tag 接口中的所有方法实现。
Tag 接口的方法:
doStartTag() 、 doEndTag() 、 getParent() 、 setParent() 、 release() 、 setPageContext()
在 Tag 类代码中不能像 jsp 一样,直接使用 out 隐含对象,他有一个对象可以使用 pageContext ,通过它的 getOut() 方法可以得到 out 对象。在标签内部,访问任何的隐含对象,都是通过调用 pageContext 的 set 方法。
如果要实现这个接口,可以通过扩展 TagSupport 这个类,来写自己需要的方法,而不需要把 Tag 接口中的所有方法实现。
Tag 接口的方法:
doStartTag() 、 doEndTag() 、 getParent() 、 setParent() 、 release() 、 setPageContext()
在 Tag 类代码中不能像 jsp 一样,直接使用 out 隐含对象,他有一个对象可以使用 pageContext ,通过它的 getOut() 方法可以得到 out 对象。在标签内部,访问任何的隐含对象,都是通过调用 pageContext 的 set 方法。
2.IterationTag
IterationTag 接口与 Tag 接口类似,用于当一个自定义标签需要重复计算它的代码体的情况下。它扩展 Tag 接口并实现了一个新的方法 doAfterBody() 来实现循环,这个方法只有从 doStartTag() 返回 EVAL_BODY_INCLUDE 时才被调用。在执行 doAfterBody() 方法时,如果返回的是 EVAL_BODY_AGAIN ,那么将再次执行 doAfterBody() 方法,直到 doAfterBody() 返回的是 SKIP_BODY 或者 EVAL_BODY_INCLUDE 。
IterationTag 接口与 Tag 接口类似,用于当一个自定义标签需要重复计算它的代码体的情况下。它扩展 Tag 接口并实现了一个新的方法 doAfterBody() 来实现循环,这个方法只有从 doStartTag() 返回 EVAL_BODY_INCLUDE 时才被调用。在执行 doAfterBody() 方法时,如果返回的是 EVAL_BODY_AGAIN ,那么将再次执行 doAfterBody() 方法,直到 doAfterBody() 返回的是 SKIP_BODY 或者 EVAL_BODY_INCLUDE 。
3.BodyTag
BodyTag 接口扩展了 IterationTag 并提供了对代码体内容进行操作的功能。就是在计算代码体的时候可以对已经形成的代码体进行修改。 BodyContent 对象就是用来保存对自定义标签体计算的结果。它有一个新方法 doInitBody() ,这个方法只有在 doStartTag() 方法返回 EVAL_BODY_BUFFERED 时才调用,此时它将创建一个 BodyContent 对象保存结果。
BodyTag 接口扩展了 IterationTag 并提供了对代码体内容进行操作的功能。就是在计算代码体的时候可以对已经形成的代码体进行修改。 BodyContent 对象就是用来保存对自定义标签体计算的结果。它有一个新方法 doInitBody() ,这个方法只有在 doStartTag() 方法返回 EVAL_BODY_BUFFERED 时才调用,此时它将创建一个 BodyContent 对象保存结果。
扩展自定义标签:
添加属性
首先要在 tld 文件中加入一个属性元素,然后在 java 文件中需要定义这个属性以及它的的 setter 方法。属性 <attribute> 元素有四个子元素分别是 <name> 、 <required> 、 <rtexprvalue> 、 <description> ,这里 <rtexprvalue> 表示的是属性是否接受 scriptlet 表达式的计算结果,默认情况下为 false ,即只能接受静态值。
添加属性
首先要在 tld 文件中加入一个属性元素,然后在 java 文件中需要定义这个属性以及它的的 setter 方法。属性 <attribute> 元素有四个子元素分别是 <name> 、 <required> 、 <rtexprvalue> 、 <description> ,这里 <rtexprvalue> 表示的是属性是否接受 scriptlet 表达式的计算结果,默认情况下为 false ,即只能接受静态值。
添加变量
可以在 tld 文件中给自定义标签加入一个 <variable> 元素,它的子元素包括 <name-given> 表示保存变量的名字, <variable-class> 表示变量的 java 类型, <declared> 用 boolean 表示这个变量是否为新的, <scope> 表示变量的使用范围( AT_BEGIN 表示从起始标签起, AT_END 表示从终止标签后, NESTED 表示起始标签和终止标签之间)。定义了变量之后,需要在 java 文件中把这个变量用 pageContext.setAttribute("",object); 这里 key 值应该就是变量对外的名字。
可以在 tld 文件中给自定义标签加入一个 <variable> 元素,它的子元素包括 <name-given> 表示保存变量的名字, <variable-class> 表示变量的 java 类型, <declared> 用 boolean 表示这个变量是否为新的, <scope> 表示变量的使用范围( AT_BEGIN 表示从起始标签起, AT_END 表示从终止标签后, NESTED 表示起始标签和终止标签之间)。定义了变量之后,需要在 java 文件中把这个变量用 pageContext.setAttribute("",object); 这里 key 值应该就是变量对外的名字。
使用
TagExtraInfo
(
TEI
)类
这个对象中有两类对象可以使用, TagData (保存标签属性的信息)、 VariableInfo (描述代码变量)
一段 TagExtraInfo 类代码实例:
public VariableInfo[] getVariableInfo(TagData data) {
String variableName = data.getAttributeString("name");
VariableInfo vi =
new VariableInfo(variableName,"String []", true, VariableInfo.AT_END);
VariableInfo[] tagVariables = new VariableInfo[1];
tagVariables[0] = vi;
return tagVariables;
}
可以通过 TagData 类的 getAttributeString 方法得到某个属性的值,还有另外一个方法 getAttribute 也是得到某个属性的值不过返回的是一个对象。而 getVariableInfo 方法必须返回一个 VariableInfo 数组。除此之外,还需要在 tld 中的元素定义 <tag-class> 后加入一个 <tei-class> 元素,说明 TEI 类的全称。
这个对象中有两类对象可以使用, TagData (保存标签属性的信息)、 VariableInfo (描述代码变量)
一段 TagExtraInfo 类代码实例:
public VariableInfo[] getVariableInfo(TagData data) {
String variableName = data.getAttributeString("name");
VariableInfo vi =
new VariableInfo(variableName,"String []", true, VariableInfo.AT_END);
VariableInfo[] tagVariables = new VariableInfo[1];
tagVariables[0] = vi;
return tagVariables;
}
可以通过 TagData 类的 getAttributeString 方法得到某个属性的值,还有另外一个方法 getAttribute 也是得到某个属性的值不过返回的是一个对象。而 getVariableInfo 方法必须返回一个 VariableInfo 数组。除此之外,还需要在 tld 中的元素定义 <tag-class> 后加入一个 <tei-class> 元素,说明 TEI 类的全称。
pageContext
对象中含有的方法包括:
getOut();getPage();getRequest();getResponse();getServletConfig();getServletContext();getSession();
Tag
接口中的返回常数意义:
EVAL_BODY_INCLUDE :告诉服务器正文的内容,并把这些内容送入输出流
SKIP_BODY :告诉服务器不要处理正文内容
EVAL_PAGE :让服务器继续执行页面
SKIP_PAGE :让服务器不要处理剩余的页面
EVAL_BODY_AGAIN :让服务器继续处理正文内容,只有 doAfterBody 方法可以返回
EVAL_BODY_BUFFERED : BodyTag 接口的字段,在 doStartTag() 返回
EVAL_BODY_INCLUDE 、 SKIP_BODY 一般由 doStartTag() 返回,而 EVAL_PAPGE 、 SKIP_PAGE 由 doEndTag() 返回。
EVAL_BODY_INCLUDE :告诉服务器正文的内容,并把这些内容送入输出流
SKIP_BODY :告诉服务器不要处理正文内容
EVAL_PAGE :让服务器继续执行页面
SKIP_PAGE :让服务器不要处理剩余的页面
EVAL_BODY_AGAIN :让服务器继续处理正文内容,只有 doAfterBody 方法可以返回
EVAL_BODY_BUFFERED : BodyTag 接口的字段,在 doStartTag() 返回
EVAL_BODY_INCLUDE 、 SKIP_BODY 一般由 doStartTag() 返回,而 EVAL_PAPGE 、 SKIP_PAGE 由 doEndTag() 返回。
在调用
doStartTag()
方法之前其实标记还调用了其他两个方法:
setPageContext()
和
setParent()
;所以在后面的方法中可以使用
pageContext
和
parent
对象,如果需要的话。
让自定义标签在页面中创建对象时必须使用一个标准的
JSP
对象
TagExtraInfo
类,它可以创建脚本变量还可以在编译的时候对标签进行检验,
TEI
类仅可以生成由
setAttribute
方法存储在
PageContext
对象中的变量,而并不是单独生成变量。
通过
TEI
类定义脚本变量可以让使用者自己定义在页面中使用对象的名称。
除了使用 TEI 类方法之外,还可以简单的在 TLD 中定义一个 <variable> 对象来使用自定义对象,用法如下:
<variable>
<name-from-attribute>name</name-from-attribute>
<variable-class>String []</variable-class>
<declare>true</declare>
<scope>AT_END</scope>
</variable>
对于 variable 的子元素, <name-from-attribute> 指的是创建的变量名称从属性 name 中来取得,当然也可以通过 <name-given> 元素来限制变量的名称。注意这两个元素是互斥的。
除了使用 TEI 类方法之外,还可以简单的在 TLD 中定义一个 <variable> 对象来使用自定义对象,用法如下:
<variable>
<name-from-attribute>name</name-from-attribute>
<variable-class>String []</variable-class>
<declare>true</declare>
<scope>AT_END</scope>
</variable>
对于 variable 的子元素, <name-from-attribute> 指的是创建的变量名称从属性 name 中来取得,当然也可以通过 <name-given> 元素来限制变量的名称。注意这两个元素是互斥的。
一个扩展
BodyTagSupport
的自定义标记的生命周期如下:
1. 创建标记
2. 调用 Setter 方法
3. 调用 doStartTag() 方法
4. 调用 setBodyContent() 方法
5. 调用 InitBody() 方法
6. 处理标记的 Body
7.doAfterBody() ;根据返回值,如果为 EVAL_BODY_AGAIN ,继续执行 6 ,如果不是,执行 8
8. 调用 doEndTag() 方法
9. 判断标记是否需要重用,如果要,执行 4 ;否则执行 release() 方法。
1. 创建标记
2. 调用 Setter 方法
3. 调用 doStartTag() 方法
4. 调用 setBodyContent() 方法
5. 调用 InitBody() 方法
6. 处理标记的 Body
7.doAfterBody() ;根据返回值,如果为 EVAL_BODY_AGAIN ,继续执行 6 ,如果不是,执行 8
8. 调用 doEndTag() 方法
9. 判断标记是否需要重用,如果要,执行 4 ;否则执行 release() 方法。
TagSupport
类的方法
findAncestorWithClass()
方法可以用来查找指定的父类,它有两个参数一个为本身的类名,还有一个就是要查找的父类的名称,如果没有返回
null
;例如
ParentTag parent = (ParentTag) this.findAncestorWithClass(this,ParentTag.class);
自定义标记的验证方法:
JSP1.1
TEI 类可以在编译时刻检验自己的标记,这个类中有一个 isValid() 方法,如果 TLD 中为这个标记定义了这个 TEI 类,那么网页在编译的时候将会调用这个方法,并且会传入一个包含属性具体内容的参数 TagData 。(在 JSP1.2 中同样有效)
JSP1.2
JSP1.2 中引入一个新的标记检验方法,定义了一个新类 TagLibraryValidator ,并且可以由此派生出检验标志的类,大多数情况下仅使用这个类的 validate() 方法,它有三个参数: prefix (在 taglib 指令中定义的前缀); uri ( TLD 文件中的 URI ); page ( JSP 页的 PageData XML 版本), validate() 方法返回值为 null 时表示验证成功,否则返回的 String 类型将是一个错误信息。
当 validator 在 TLD 文件中定义时,它应该放在 <tag> 元素定义的外面,因为它是用来处理验证标记库中的所有标记的。
<validator><validator-class></validator-class></validator> 。
JSP1.1
TEI 类可以在编译时刻检验自己的标记,这个类中有一个 isValid() 方法,如果 TLD 中为这个标记定义了这个 TEI 类,那么网页在编译的时候将会调用这个方法,并且会传入一个包含属性具体内容的参数 TagData 。(在 JSP1.2 中同样有效)
JSP1.2
JSP1.2 中引入一个新的标记检验方法,定义了一个新类 TagLibraryValidator ,并且可以由此派生出检验标志的类,大多数情况下仅使用这个类的 validate() 方法,它有三个参数: prefix (在 taglib 指令中定义的前缀); uri ( TLD 文件中的 URI ); page ( JSP 页的 PageData XML 版本), validate() 方法返回值为 null 时表示验证成功,否则返回的 String 类型将是一个错误信息。
当 validator 在 TLD 文件中定义时,它应该放在 <tag> 元素定义的外面,因为它是用来处理验证标记库中的所有标记的。
<validator><validator-class></validator-class></validator> 。
比较
JSP1.2
和
JSP1.1
中的方法:
TagLibraryValidator 比 TEI 类更全面,可以用来检测整个网页,而不仅仅是标记本身,可以用来处理标记间的合作,并且这种方法可以用来通知程序员错误出在哪里,但是同时它的方法也比 TEI 类的方法复杂多了,因为它需要遍历整个 XML 版本的 JSP (完成 getAttributeValue 方法)。
TagLibraryValidator 比 TEI 类更全面,可以用来检测整个网页,而不仅仅是标记本身,可以用来处理标记间的合作,并且这种方法可以用来通知程序员错误出在哪里,但是同时它的方法也比 TEI 类的方法复杂多了,因为它需要遍历整个 XML 版本的 JSP (完成 getAttributeValue 方法)。
JSP1.2
中的
TryCatchFinally
接口:
这个接口主要是用于当自定义标记出现异常时释放自定义标记中的资源使用的,它定义了两个方法:
public void doCatch(Throwable t); (当 doStartTag,doInitBody,doAfterBody,doEndTag 方法出现异常时会调用这个方法)
piblic void doFinally(); (当 doEndTag 被调用后,无论是否出现异常都会调用这个方法,就像程序中的 finally 块,可以用来释放资源)
这个接口主要是用于当自定义标记出现异常时释放自定义标记中的资源使用的,它定义了两个方法:
public void doCatch(Throwable t); (当 doStartTag,doInitBody,doAfterBody,doEndTag 方法出现异常时会调用这个方法)
piblic void doFinally(); (当 doEndTag 被调用后,无论是否出现异常都会调用这个方法,就像程序中的 finally 块,可以用来释放资源)
在
JSP1.2
中,可以通过在
tld
文件中加入一个元素
<uri></uri>
来指定自己的在
taglib
指令中使用的名称,然后把这个
tld
文件与
Manifest.mf
一起放在
META-INF
目录中,那么在页面中就可以非常方便地导入这些
tld
。
编写自定义标记的原则:
1. 使用脚本变量(允许设计者为脚本变量起名、将脚本变量的数量减到最小、使用一个组合脚本对象和存取函数即使用 JavaBean )
2. 当设计相互协作的标记时应该尽量避免创建一套新的语言,应当尽量使用脚本变量
3. 编写代码而不是内容,不要在自定义标记中产生 HTML ,这样会失去通用性
1. 使用脚本变量(允许设计者为脚本变量起名、将脚本变量的数量减到最小、使用一个组合脚本对象和存取函数即使用 JavaBean )
2. 当设计相互协作的标记时应该尽量避免创建一套新的语言,应当尽量使用脚本变量
3. 编写代码而不是内容,不要在自定义标记中产生 HTML ,这样会失去通用性