◆4. 定义Tags
如果要定义一个tag,用户需要做的工作是:
■ 为这个tag开发一个tag handler和helper类
■ 在TLD(Tag Library Descriptors)中定义这个tag
这一部分描述了tag handler和TLD的特征,也介绍了如何为前面介绍的每一种类型的
tag定义tag handlers和库描述符elements。
◆4.1 Tag Handlers
当Web container执行一个JSP的时候,如果JSP引用了一个自定义的tag,那么就需要调
用tag handler对象。Tag handlers必须实现Tag或者BodyTag接口,这两个接口可以使
已经存在的Java对象成为tag handler对象。对于新创建的handlers,用户可以使用
TagSupport或者TagBodySupport作为基类,上面提到的类和接口都包含在:
javax.servlet.jsp.tagext包中。
JSP的servlet在处理tag的不同点调用不同的Tag handler方法,这些方法在Tag或者
BodyTag接口中都有定义。当JSP的servlet遇到自定义tag的开始tag的时候,servlet首
先调用方法初始化合适的handler,然后调用handler的doStartTag()方法;当JSP的
servlet遇到自定义tag的结束tag的时候,servlet调用handler的doEndTag()方法。其
他的方法在handler需要同tag的body相互作用的时候调用。更详细的信息,参考[Tag
Handler是如何被调用的]部分。为了提供一个Tag handler的完整实现,用户必须实现
下面总结的这些方法,这些方法在处理tag的不同阶段使用。
---------------------------------------------
Tag handler类型 方法
- - - - - - - - - - - - - - - - - - - - - - -
简单 doStartTag,doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
包含属性 doStartTag,doEndTag, set/getProperties, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,没有相互作用 doStartTag,doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,重复处理 doStartTag,doAfterBody, doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,有相互作用 doStartTag, doEndTag, release,doInitBody,
doAfterBody
---------------------------------------------
一个Tag handler可以通过一个API来同JSP页面相互作用,这个API的入口点是page
context object(javax.servlet.jsp.PageContext),Tag handler通过它使用JSP中
可用的所有隐含的对象,例如:request, session和application。隐含的对象可以有
已命名的属性,这些属性可以用set/getProperty()方法访问。如果tag是嵌套的,那么
handler也可以访问父tag的handler。一系列相关的tag handlers类通常打包为一个JAR
文件发布。
◆4.2 Tag库描述符
TLD是描述Tag库的XML文档,一个TLD文档包含Tag库整体的信息,以及Tag库包含的所有
tags的信息,Web container可以用TLD来验证tags,JSP的开发用具也可以使用TLD。
TLD文件必须有一个扩展名为:.tld,TLD文件保存在WAR文件的WEB-INF目录或者
WEB-INF目录的子目录。如果用户使用“deploytool”给WAR文件添加TLD文件,那么它
们会自动添加到WEB-INF目录下。一个TLD文件的内容必须以XML文档的序言
(prologue)开始,这个序言用来指定XML的版本和文档类型定义(DTD, Document
Type Definition):
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD JSP Tag Library1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
J2EE SDK 版本1.3支持版本1.1和1.2的DTDs,因为用户开发的时候应该使用新的DTD版
本,所以这个文档只适用DTD Ver1.2。TLD的root是taglib element,taglib element
的子element在下面列出:
---------------------------------------------
Element 描述
- - - - - - - - - - - - - - - - - - - - - - -
tlib-version tag库的版本
jsp-version tag库需要的JSP规范的版本
short-name 可选,JSP的开发者可以使用一个容易记忆的名字
uri 一个URI,它独一无二的确定一个tag库
display-name 可选,开发工具使用的显示名字
small-icon 可选,开发工具使用的小图标
large-icon 可选,开发工具使用的大图标
description 可选,关于tag的详细的信息
listener 参考下面的[listener Element]
tag 参考下面的[tag Element]
---------------------------------------------
◆4.2.1 listener Element
tag库可以指定一些Java类作为消息的listener(参考[Handling Servlet's
Life-Circle Events])。这些listener在TLD中用listener Element列出,Web
container实例化这些listener类,并且用类似于WAR级别的listeners的注册方法注
册,但是同WAR级别的listeners不同的是:TLD中的listener注册顺序是不确定的。
listener Element唯一的子Element是listener-class element,这个element必须包含
完整的listener类路径。
◆4.2.2 tag Element
Tag库中描述一个tag需要的信息有:tag名、tag handler的类名、tag创建的脚本变量
的信息、tag的属性信息。脚本变量的信息可以被直接给定,也可以通过其他的tag信息
类(参考:[定义脚本变量的Tags])。Tag的每个属性需要指定这个tag是否是必需的、
是否能通过request-time表达式来决定属性值和tag的属性类型(参考:[有属性的
Tags])。Tag在TLD中使用tag Element来指定,下面列出的是tag Element允许的子
element:
---------------------------------------------
Element 描述
- - - - - - - - - - - - - - - - - - - - - - -
name 唯一的tag名
tag-class Tag handler的类名
tei-class 可选,javax.servlet.jsp.tagext.TagExtraInfo的子类
body-content Body内容的类型
display-name 可选,开发工具使用的显示名字
small-icon 可选,开发工具使用的小图标
large-icon 可选,开发工具使用的大图标
description 可选,关于tag的详细的信息
variable 可选,脚本变量信息
attribute Tag的属性信息
---------------------------------------------
下面的部分是关于开发[使用Tags]中介绍的每一种类型的tag的方法和TLD element。
◆4.3 简单的Tags
◆4.3.1 Tag Handlers
简单tag的handler必须实现Tag接口的doStartTag()和doEndTag()方法,doStartTag()
在开始tag解析的时候调用,这个方法返回SKIP_BODY,因为简单tag没有body,
doEndTag()在结束tag解析的时候调用,如果JSP页面剩下的部分还需要解析,那么返回
EVAL_PAGE,否则返回SKIP_PAGE。简单tag的使用方法:
<tt:simple />
实现的tag handler:
public SimpleTag extends TagSupport {
public int doStartTag() throws JspException {
try {
pageContext.getOut().print("Hello.");
} catch (Exception ex) {
throw new JspTagException("SimpleTag: " +
ex.getMessage());
}
return SKIP_BODY;
}
public int doEndTag() {
return EVAL_PAGE;
}
}
◆4.3.2 Body-content Element
没有body的tag在TLD中必须这样定义:
<body-content>empty</body-content>
◆4.4 有属性的Tags
◆4.4.1 在Tag Handler中定义属性
对于Tag的每个属性来说,为了符合JavaBeans的体系习惯,必须在tag handler中定义
property,还有set/get方法。例如:对于Struts的logic:present tag来说,它的使用
方法和handler应该是:
<logic:present parameter="Clear">
protected String parameter = null;
public String getParameter() {
return (this.parameter);
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
注意,如果用户的tag handler是从TagSupport继承,并且要定义的属性的名字是:
id,那么就可以省略上面提到的步骤,因为TagSupport已经定义了id属性。
如果tag的一个属性的类型是String,那么它可以同tag handler能访问的一个隐含对象
的属性同名。用户可以通过传递tag属性值给隐含对象的set/getAttribute()方法来访
问隐含对象的属性。如果脚本变量保存在pageContext对象中,那么上面提到的方法就
是一个很好的传递脚本变量名字给tag handler的方法。
◆4.4.2 属性Element
对于每个tag属性,用户必须指定这个属性是否是必需的、属性值是否可以通过表达式
给定、(可选),属性element中属性的类型。对于静态的属性值,属性类型始终是
java.lang.String。如果rtexprvalue element的值是true/yes,那么type element决
定表达式返回的属性值的类型。例如:
<attribute>
<name>attr1</name>
<required>true|false|yes|no</required>
<rtexprvalue>true|false|yes|no</rtexprvalue>
<type>fully_qualified_type</type>
</attribute>
如果tag的属性不是必需的,那么tag handler需要为这个属性提供一个缺省值。对于
logic:present tag来说,它的parameter属性不是必需的,并且属性值可以使用运行时
候的表达式提供:
<tag>
<name>present</name>
<tag-class>org.apache.struts.taglib.
logic.PresentTag</tag-class>
<body-content>JSP</body-content>
...
<attribute>
<name>parameter</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
...
</tag>
◆4.4.3 属性校验
这一部分内容说明如何验证tag属性值是否合法,这样当JSP编译的时候,Web
container就可以用TLD中的定义对属性值进行限制。传递给tag属性值也可以在JSP编
译的时候使用isValid()方法进行验证,这个方法是从TagExtraInfo继承的子类的方法
,这个类也可以为tag定义的脚本变量提供信息(参考:[定义脚本变量的Tags])。可
以通过TagData对象给isValid()方法传递属性信息,这个对象包含tag的每一个属性的
值,需要在request-time计算的属性值将被赋值为:TagData.REQUEST_TIME_VALUE。例
如:
JSP定义:
<tt:twa attr1="value1"/>
TLD定义:
<attribute>
<name>attr1</name>
<required>true</required>
<rtexprvalue>true</a>
</attribute>
这个定义表明attr1的值在运行时候决定。下面的isValid()方法检查属性attr1的值是
否是Boolean型。注意:因为属性attr1的值在运行时候计算,isValid()方法必须检查
tag的用户是否选择提供一个运行时候的值,例如:
public class TwaTEI extends TagExtraInfo {
public boolean isValid(Tagdata data) {
Object o = data.getAttribute("attr1");
if (o != null && o != TagData.REQUEST_TIME_VALUE) {
if (o.toLowerCase().equals("true") ||
o.toLowerCase().equals("false") )
return true;
else
return false;
}
else
return true;
}
}
◆4.5 有Body的Tags
◆4.5.1 Tag Handlers
Tag Handler同body相互作用(interact)与否会影响带有body的tag handler的具体实
现的,相互作用意味着tag handler读取或者修改body的内容。
◆4.5.1.1 Tag Handler同body没有相互作用
如果tag handler不需要同body相互作用,那么tag handler需要实现Tag接口(或者从
TagSupport继承)。如果tag的body需要被解析,那么doStartTag()方法需要返回
EVAL_BODY_INCLUDE,否则返回SKIP_BODY。如果tag handler需要循环的解析body,那
么tag handler需要实现IterationTag(或者从TagSupport继承)。如果body内容需要
重新解析,那么doStartTag()和doAfterBody()方法需要返回EVAL_BODY_AGAIN。
◆4.5.1.2 Tag Handler同body存在相互作用
如果tag handler需要同body相互作用,那么tag handler需要实现BodyTag接口(或者
从BodyTagSupport继承)。典型的tag handler需要实现doInitBody()和doAfterBody()
方法,这些方法同JSP的servlet传递给tag handler的body内容相互作用。Body
content支持几种读写body的内容的方法。tag handler可以使用BodyContent的
getString()和getReader()方法从body中读取信息,使用writeOut(out)方法写body的
内容到输出流中。提供writeOut()方法的writer对象是通过tag handler的
getPreviousOut()方法得到的,这个方法可以保证tag内嵌套的tag handler可以访问
父tag handler的结果。如果body的内容需要计算,那么doStartTag()方法需要返回
EVAL_BODY_BUFFERED,否则返回SKIP_BODY。
■ doInitBody()方法
doInitBody()方法在body的内容被设定后、计算前调用,用户可以用这个方法做一些依
赖于body内容的初始化工作。
■ doAfterBody()方法
doAfterBody()方法在body的内容被计算后调用,像doStartTag()方法一样,这个方法
必须返回一个标志表明是否需要继续计算body的内容,因此,如果body的内容需要重新
计算,就是说用户实现了一个iteration tag,那么doAfterBody()返回
EVAL_BODY_BUFFERED,否则返回SKIP_BODY。
public class QueryTag extends BodyTagSupport {
public int doAfterBody() throws JspTagException {
BodyContent bc = getBodyContent();
// get the bc as string
String query = bc.getString();
// clean up
bc.clearBody();
try {
Statement stmt = connection.createStatement();
result = stmt.executeQuery(query);
} catch (SQLException e) {
throw new JspTagException("QueryTag: " +
e.getMessage());
}
return SKIP_BODY;
}
}
■ release()方法
Tag handler应该在release()方法重新设置状态和释放私有的资源。下面的例子读取
body的内容(包含一个SQL查询的字符串),然后将它传递给一个执行查询工作的对
象,因为body的内容不需要重新计算,所以doAfterBody()方法返回SKIP_BODY:
◆4.5.2 body-content Element
对于有body的tags,用户必须使用body-content Element指定body内容的类型:
<body-content>JSP|tagdependent</body-content>
Body的内容包含的自定义或者核心tags、脚本变量、HTML文本被分类为“JSP”,这就
是Struts的logic:present tag定义的值,所有其他类型的值--传递给查询tag的SQL文
本--将被分类为tagdependent。注意:body-content Element的值不影响tag handler
对body内容的解释,这个element的值只是被JSP编辑工具用来决定如何显示body的内
容。
◆4.6 定义脚本变量的Tags
◆4.6.1 Tag Handlers
Tag handler的责任是创建和设定一个脚本变量引用的对象,这个脚本变量是保存在JSP
页面可以访问的context中的,tag handler可以通过:
pageContext.setAttribute(name,value,scope)或者
pageContext.setAttribute(name,value)
方法完成这个工作。一般情况下:传递给自定义tag的属性指定脚本变量对象的名字,
这个名字可以通过属性的get方法返回。如果脚本变量的值依赖于存在在tag handler的
context的对象,那么可以使用pageContext.getAttribute(name,scope)方法。一般的处
理方式是tag handler返回一个脚本变量,进行一些处理,然后使用
pageContext.setAttribute(name,object)方法设定脚本变量的值。对象的scope属性在
下面列出,包括访问范围和生命周期:
---------------------------------------------------
名字 访问源 生命周期
- - - - - - - - - - - - - - - - - - - - - - - - - -
page 当前页面 处理离开当前页面
- - - - - - - - - - - - - - - - - - - - - - - - - -
request 当前页面和include/ response返回到用户
forward页面
- - - - - - - - - - - - - - - - - - - - - - - - - -
session 同session 同session
- - - - - - - - - - - - - - - - - - - - - - - - - -
application 同WEB应用程序 同WEB应用程序
---------------------------------------------------
◆4.6.2 提供关于脚本变量的信息
下面的例子定义了一个脚本变量book用来访问图书的信息:
<bean:define id="book" name="bookDB" property="bookDetails"
type="database.BookDetails"/>
<font color="red" size="+2">
<%=messages.getString("CartRemoved")%>
<strong><jsp:getProperty name="book"
property="title"/></strong>
<br> <br>
</font>
当包含上面的自定义tag的JSP被解释的时候,Web container同时为脚本变量和脚本变
量引用的对象生成代码,为了生成代码,Web container需要确定的信息有:
■ 变量名
■ 变量class
■ 变量是否引用一个新的或者已存在的对象
■ 变量的可用范围
有两种方法可以提供上面提到的信息:在TLD中指定variable element、在TLD中指定
tei-class并且定义一个提供tag其他信息的class。使用variable element的方法很简
单,但是不够灵活。
◆4.6.2.1 variable Element
variable Element有下面列出的子elements:
■ name-given,作为常数的变量名
■ name-from-attribute,变量名是一个属性的值,在JSP解释时确定。
name-given和name-from-attribute两者之间必须指定其中的一个,下面的子elements
是可选择的:
■ variable-class,变量的class名,java.lang.String是缺省值
■ declare,是否变量引用一个新的对象,缺省值是true
■ scope,变量定义的范围,缺省值是NESTED,下面的表格定义了变量的可用范围和设
定方法。
---------------------------------------------------
值 可用范围 方法
- - - - - - - - - - - - - - - - - - - - - - - - - -
NESTED 开始和结束tag之间 实现BodyTag,doInitBody()和doAfterBody(),
没有实现BodyTag,doStartTag()
- - - - - - - - - - - - - - - - - - - - - - - - - -
AT_BEGIN 从开始tag到页面结束 实现BodyTag,doInitBody()和doAfterBody(),
doEndTag(),没有实现BodyTag,doStartTag(),
doEndTag()
- - - - - - - - - - - - - - - - - - - - - - - - - -
AT_END 从结束tag到页面结束 doEndTag()
---------------------------------------------------
Struts bean:define tag的实现遵守JSP的规范 ver1.1,它需要用户定义一个
TagExtraInfo子类,JSP的规范 ver1.2添加了variable element,用户应该在
bean:define tag中这样定义variable element:
<tag>
<variable>
<name-from-attribute>id</name-from-attribute>
<variable-class>database.BookDetails</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag>
◆4.6.2.2 TagExtraInfo Class
用户通过继承javax.servlet.jsp.TagExtraInfo来实现一个子类,子类必须实现
getVariableInfo()方法,这个方法返回一个包含下列信息的VariableInfo类型的数组
:
■ 变量名
■ 变量class
■ 变量是否引用一个新的或者已存在的对象
■ 变量的可用范围
Web container传递一个叫data的参数给getVariableInfo()方法,data参数包含tag的
所有的属性的属性值,这些属性被VariableInfo对象使用,VariableInfo对象包含变量
的名字和Java class。Struts tag库使用DefineTei类为bean:define tag创建的脚本变
量提供信息。因为脚本变量的名字和Java class是通过tag的属性传递,所以它们可以
通过data.getAttributeString()方法返回,并且传递给VariableInfo的构造函数。为
了允许变量book在页面的余下部分使用,变量的scope被设置为AT_BEGIN,如下面的所
示:
public class DefineTei extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData data) {
String type = data.getAttributeString("type");
if (type == null)
type = "java.lang.Object";
return new VariableInfo[] {
new VariableInfo(data.getAttributeString("id"),
type,
true,
VariableInfo.AT_BEGIN)
};
}
}
脚本变量的TagExtraInfo的Java class名字必须在TLD的tag element的tei-class子
element中定义,因此对于DefineTei的定义应该是:
<tei-class>org.apache.struts.taglib.bean.DefineTagTei
</tei-class>
◆4.7 协作(cooperating)Tags
Tags之间通过共享对象相互作用,JSP技术支持两种类型的对象共享:第一种类型是共
享对象被命名然后保存到pageContext中(可以被JSP页面和tag handlers访问的隐含对
象),如果访问被其他tag命名和创建的对象,tag handlers使用
page.getAttribute(name,scope)方法。第二种类型是对象共享,一组嵌套tags的父tag
创建的对象对于所有的内嵌tag handlers都是可用的,这种类型的对象共享的好处是使
用私有命名空间为对象命名,这样就减少了潜在的命名冲突。为了访问父tag创建的对
象,tag handler必须通过静态的TagSupport.findAncestorWithClass(from,class)方
法得到包含的tag,或者使用TagSupport.getParent()方法。前一种方法是在嵌套模式
不能保证的情况下使用,一旦父tag返回,tag handler可以得到任何静态的和动态的创
建的对象,静态创建的对象是父tag的成员变量。Private的对象也可以被动态创建,这
些对象可以使用setValue()方法保存在tag handler中,也可以使用getValue()方法返
回。
下面的例子解释了一个tag的tag handler既支持命名方法,也支持private对象的方法
来共享对象。例子中查寻tag的handler检查了connection属性是否在doStartTag()中设
定,如果connection属性已经设定,handler从pageContext中得到connection对象,否
则,handler首先得到父tag的handler,然后从中返回connection对象:
public class QueryTag extends BodyTagSupport {
private String connectionId;
public int doStartTag() throws JspException {
String cid = getConnection();
if (cid != null) {
// there is a connection id, use it
connection =(Connection)pageContext.
getAttribute(cid);
} else {
ConnectionTag ancestorTag =
(ConnectionTag)findAncestorWithClass(this,
ConnectionTag.class);
if (ancestorTag == null) {
throw new JspTagException("A query without
a connection attribute must be nested
within a connection tag.");
}
connection = ancestorTag.getConnection();
}
}
}
实现查询tag的handler可以用下面的两种方法使用:
<tt:connection id="con01" ....> ... </tt:connection>
<tt:query id="balances" connection="con01">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</tt:query>
<tt:connection ...>
<x:query id="balances">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</x:query>
</tt:connection>
TLD中tag handler的定义必须指明connection属性是可选的:
<tag>
...
<attribute>
<name>connection</name>
<required>false</required>
</attribute>
</tag>
如果要定义一个tag,用户需要做的工作是:
■ 为这个tag开发一个tag handler和helper类
■ 在TLD(Tag Library Descriptors)中定义这个tag
这一部分描述了tag handler和TLD的特征,也介绍了如何为前面介绍的每一种类型的
tag定义tag handlers和库描述符elements。
◆4.1 Tag Handlers
当Web container执行一个JSP的时候,如果JSP引用了一个自定义的tag,那么就需要调
用tag handler对象。Tag handlers必须实现Tag或者BodyTag接口,这两个接口可以使
已经存在的Java对象成为tag handler对象。对于新创建的handlers,用户可以使用
TagSupport或者TagBodySupport作为基类,上面提到的类和接口都包含在:
javax.servlet.jsp.tagext包中。
JSP的servlet在处理tag的不同点调用不同的Tag handler方法,这些方法在Tag或者
BodyTag接口中都有定义。当JSP的servlet遇到自定义tag的开始tag的时候,servlet首
先调用方法初始化合适的handler,然后调用handler的doStartTag()方法;当JSP的
servlet遇到自定义tag的结束tag的时候,servlet调用handler的doEndTag()方法。其
他的方法在handler需要同tag的body相互作用的时候调用。更详细的信息,参考[Tag
Handler是如何被调用的]部分。为了提供一个Tag handler的完整实现,用户必须实现
下面总结的这些方法,这些方法在处理tag的不同阶段使用。
---------------------------------------------
Tag handler类型 方法
- - - - - - - - - - - - - - - - - - - - - - -
简单 doStartTag,doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
包含属性 doStartTag,doEndTag, set/getProperties, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,没有相互作用 doStartTag,doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,重复处理 doStartTag,doAfterBody, doEndTag, release
- - - - - - - - - - - - - - - - - - - - - - -
有body,有相互作用 doStartTag, doEndTag, release,doInitBody,
doAfterBody
---------------------------------------------
一个Tag handler可以通过一个API来同JSP页面相互作用,这个API的入口点是page
context object(javax.servlet.jsp.PageContext),Tag handler通过它使用JSP中
可用的所有隐含的对象,例如:request, session和application。隐含的对象可以有
已命名的属性,这些属性可以用set/getProperty()方法访问。如果tag是嵌套的,那么
handler也可以访问父tag的handler。一系列相关的tag handlers类通常打包为一个JAR
文件发布。
◆4.2 Tag库描述符
TLD是描述Tag库的XML文档,一个TLD文档包含Tag库整体的信息,以及Tag库包含的所有
tags的信息,Web container可以用TLD来验证tags,JSP的开发用具也可以使用TLD。
TLD文件必须有一个扩展名为:.tld,TLD文件保存在WAR文件的WEB-INF目录或者
WEB-INF目录的子目录。如果用户使用“deploytool”给WAR文件添加TLD文件,那么它
们会自动添加到WEB-INF目录下。一个TLD文件的内容必须以XML文档的序言
(prologue)开始,这个序言用来指定XML的版本和文档类型定义(DTD, Document
Type Definition):
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD JSP Tag Library1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
J2EE SDK 版本1.3支持版本1.1和1.2的DTDs,因为用户开发的时候应该使用新的DTD版
本,所以这个文档只适用DTD Ver1.2。TLD的root是taglib element,taglib element
的子element在下面列出:
---------------------------------------------
Element 描述
- - - - - - - - - - - - - - - - - - - - - - -
tlib-version tag库的版本
jsp-version tag库需要的JSP规范的版本
short-name 可选,JSP的开发者可以使用一个容易记忆的名字
uri 一个URI,它独一无二的确定一个tag库
display-name 可选,开发工具使用的显示名字
small-icon 可选,开发工具使用的小图标
large-icon 可选,开发工具使用的大图标
description 可选,关于tag的详细的信息
listener 参考下面的[listener Element]
tag 参考下面的[tag Element]
---------------------------------------------
◆4.2.1 listener Element
tag库可以指定一些Java类作为消息的listener(参考[Handling Servlet's
Life-Circle Events])。这些listener在TLD中用listener Element列出,Web
container实例化这些listener类,并且用类似于WAR级别的listeners的注册方法注
册,但是同WAR级别的listeners不同的是:TLD中的listener注册顺序是不确定的。
listener Element唯一的子Element是listener-class element,这个element必须包含
完整的listener类路径。
◆4.2.2 tag Element
Tag库中描述一个tag需要的信息有:tag名、tag handler的类名、tag创建的脚本变量
的信息、tag的属性信息。脚本变量的信息可以被直接给定,也可以通过其他的tag信息
类(参考:[定义脚本变量的Tags])。Tag的每个属性需要指定这个tag是否是必需的、
是否能通过request-time表达式来决定属性值和tag的属性类型(参考:[有属性的
Tags])。Tag在TLD中使用tag Element来指定,下面列出的是tag Element允许的子
element:
---------------------------------------------
Element 描述
- - - - - - - - - - - - - - - - - - - - - - -
name 唯一的tag名
tag-class Tag handler的类名
tei-class 可选,javax.servlet.jsp.tagext.TagExtraInfo的子类
body-content Body内容的类型
display-name 可选,开发工具使用的显示名字
small-icon 可选,开发工具使用的小图标
large-icon 可选,开发工具使用的大图标
description 可选,关于tag的详细的信息
variable 可选,脚本变量信息
attribute Tag的属性信息
---------------------------------------------
下面的部分是关于开发[使用Tags]中介绍的每一种类型的tag的方法和TLD element。
◆4.3 简单的Tags
◆4.3.1 Tag Handlers
简单tag的handler必须实现Tag接口的doStartTag()和doEndTag()方法,doStartTag()
在开始tag解析的时候调用,这个方法返回SKIP_BODY,因为简单tag没有body,
doEndTag()在结束tag解析的时候调用,如果JSP页面剩下的部分还需要解析,那么返回
EVAL_PAGE,否则返回SKIP_PAGE。简单tag的使用方法:
<tt:simple />
实现的tag handler:
public SimpleTag extends TagSupport {
public int doStartTag() throws JspException {
try {
pageContext.getOut().print("Hello.");
} catch (Exception ex) {
throw new JspTagException("SimpleTag: " +
ex.getMessage());
}
return SKIP_BODY;
}
public int doEndTag() {
return EVAL_PAGE;
}
}
◆4.3.2 Body-content Element
没有body的tag在TLD中必须这样定义:
<body-content>empty</body-content>
◆4.4 有属性的Tags
◆4.4.1 在Tag Handler中定义属性
对于Tag的每个属性来说,为了符合JavaBeans的体系习惯,必须在tag handler中定义
property,还有set/get方法。例如:对于Struts的logic:present tag来说,它的使用
方法和handler应该是:
<logic:present parameter="Clear">
protected String parameter = null;
public String getParameter() {
return (this.parameter);
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
注意,如果用户的tag handler是从TagSupport继承,并且要定义的属性的名字是:
id,那么就可以省略上面提到的步骤,因为TagSupport已经定义了id属性。
如果tag的一个属性的类型是String,那么它可以同tag handler能访问的一个隐含对象
的属性同名。用户可以通过传递tag属性值给隐含对象的set/getAttribute()方法来访
问隐含对象的属性。如果脚本变量保存在pageContext对象中,那么上面提到的方法就
是一个很好的传递脚本变量名字给tag handler的方法。
◆4.4.2 属性Element
对于每个tag属性,用户必须指定这个属性是否是必需的、属性值是否可以通过表达式
给定、(可选),属性element中属性的类型。对于静态的属性值,属性类型始终是
java.lang.String。如果rtexprvalue element的值是true/yes,那么type element决
定表达式返回的属性值的类型。例如:
<attribute>
<name>attr1</name>
<required>true|false|yes|no</required>
<rtexprvalue>true|false|yes|no</rtexprvalue>
<type>fully_qualified_type</type>
</attribute>
如果tag的属性不是必需的,那么tag handler需要为这个属性提供一个缺省值。对于
logic:present tag来说,它的parameter属性不是必需的,并且属性值可以使用运行时
候的表达式提供:
<tag>
<name>present</name>
<tag-class>org.apache.struts.taglib.
logic.PresentTag</tag-class>
<body-content>JSP</body-content>
...
<attribute>
<name>parameter</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
...
</tag>
◆4.4.3 属性校验
这一部分内容说明如何验证tag属性值是否合法,这样当JSP编译的时候,Web
container就可以用TLD中的定义对属性值进行限制。传递给tag属性值也可以在JSP编
译的时候使用isValid()方法进行验证,这个方法是从TagExtraInfo继承的子类的方法
,这个类也可以为tag定义的脚本变量提供信息(参考:[定义脚本变量的Tags])。可
以通过TagData对象给isValid()方法传递属性信息,这个对象包含tag的每一个属性的
值,需要在request-time计算的属性值将被赋值为:TagData.REQUEST_TIME_VALUE。例
如:
JSP定义:
<tt:twa attr1="value1"/>
TLD定义:
<attribute>
<name>attr1</name>
<required>true</required>
<rtexprvalue>true</a>
</attribute>
这个定义表明attr1的值在运行时候决定。下面的isValid()方法检查属性attr1的值是
否是Boolean型。注意:因为属性attr1的值在运行时候计算,isValid()方法必须检查
tag的用户是否选择提供一个运行时候的值,例如:
public class TwaTEI extends TagExtraInfo {
public boolean isValid(Tagdata data) {
Object o = data.getAttribute("attr1");
if (o != null && o != TagData.REQUEST_TIME_VALUE) {
if (o.toLowerCase().equals("true") ||
o.toLowerCase().equals("false") )
return true;
else
return false;
}
else
return true;
}
}
◆4.5 有Body的Tags
◆4.5.1 Tag Handlers
Tag Handler同body相互作用(interact)与否会影响带有body的tag handler的具体实
现的,相互作用意味着tag handler读取或者修改body的内容。
◆4.5.1.1 Tag Handler同body没有相互作用
如果tag handler不需要同body相互作用,那么tag handler需要实现Tag接口(或者从
TagSupport继承)。如果tag的body需要被解析,那么doStartTag()方法需要返回
EVAL_BODY_INCLUDE,否则返回SKIP_BODY。如果tag handler需要循环的解析body,那
么tag handler需要实现IterationTag(或者从TagSupport继承)。如果body内容需要
重新解析,那么doStartTag()和doAfterBody()方法需要返回EVAL_BODY_AGAIN。
◆4.5.1.2 Tag Handler同body存在相互作用
如果tag handler需要同body相互作用,那么tag handler需要实现BodyTag接口(或者
从BodyTagSupport继承)。典型的tag handler需要实现doInitBody()和doAfterBody()
方法,这些方法同JSP的servlet传递给tag handler的body内容相互作用。Body
content支持几种读写body的内容的方法。tag handler可以使用BodyContent的
getString()和getReader()方法从body中读取信息,使用writeOut(out)方法写body的
内容到输出流中。提供writeOut()方法的writer对象是通过tag handler的
getPreviousOut()方法得到的,这个方法可以保证tag内嵌套的tag handler可以访问
父tag handler的结果。如果body的内容需要计算,那么doStartTag()方法需要返回
EVAL_BODY_BUFFERED,否则返回SKIP_BODY。
■ doInitBody()方法
doInitBody()方法在body的内容被设定后、计算前调用,用户可以用这个方法做一些依
赖于body内容的初始化工作。
■ doAfterBody()方法
doAfterBody()方法在body的内容被计算后调用,像doStartTag()方法一样,这个方法
必须返回一个标志表明是否需要继续计算body的内容,因此,如果body的内容需要重新
计算,就是说用户实现了一个iteration tag,那么doAfterBody()返回
EVAL_BODY_BUFFERED,否则返回SKIP_BODY。
public class QueryTag extends BodyTagSupport {
public int doAfterBody() throws JspTagException {
BodyContent bc = getBodyContent();
// get the bc as string
String query = bc.getString();
// clean up
bc.clearBody();
try {
Statement stmt = connection.createStatement();
result = stmt.executeQuery(query);
} catch (SQLException e) {
throw new JspTagException("QueryTag: " +
e.getMessage());
}
return SKIP_BODY;
}
}
■ release()方法
Tag handler应该在release()方法重新设置状态和释放私有的资源。下面的例子读取
body的内容(包含一个SQL查询的字符串),然后将它传递给一个执行查询工作的对
象,因为body的内容不需要重新计算,所以doAfterBody()方法返回SKIP_BODY:
◆4.5.2 body-content Element
对于有body的tags,用户必须使用body-content Element指定body内容的类型:
<body-content>JSP|tagdependent</body-content>
Body的内容包含的自定义或者核心tags、脚本变量、HTML文本被分类为“JSP”,这就
是Struts的logic:present tag定义的值,所有其他类型的值--传递给查询tag的SQL文
本--将被分类为tagdependent。注意:body-content Element的值不影响tag handler
对body内容的解释,这个element的值只是被JSP编辑工具用来决定如何显示body的内
容。
◆4.6 定义脚本变量的Tags
◆4.6.1 Tag Handlers
Tag handler的责任是创建和设定一个脚本变量引用的对象,这个脚本变量是保存在JSP
页面可以访问的context中的,tag handler可以通过:
pageContext.setAttribute(name,value,scope)或者
pageContext.setAttribute(name,value)
方法完成这个工作。一般情况下:传递给自定义tag的属性指定脚本变量对象的名字,
这个名字可以通过属性的get方法返回。如果脚本变量的值依赖于存在在tag handler的
context的对象,那么可以使用pageContext.getAttribute(name,scope)方法。一般的处
理方式是tag handler返回一个脚本变量,进行一些处理,然后使用
pageContext.setAttribute(name,object)方法设定脚本变量的值。对象的scope属性在
下面列出,包括访问范围和生命周期:
---------------------------------------------------
名字 访问源 生命周期
- - - - - - - - - - - - - - - - - - - - - - - - - -
page 当前页面 处理离开当前页面
- - - - - - - - - - - - - - - - - - - - - - - - - -
request 当前页面和include/ response返回到用户
forward页面
- - - - - - - - - - - - - - - - - - - - - - - - - -
session 同session 同session
- - - - - - - - - - - - - - - - - - - - - - - - - -
application 同WEB应用程序 同WEB应用程序
---------------------------------------------------
◆4.6.2 提供关于脚本变量的信息
下面的例子定义了一个脚本变量book用来访问图书的信息:
<bean:define id="book" name="bookDB" property="bookDetails"
type="database.BookDetails"/>
<font color="red" size="+2">
<%=messages.getString("CartRemoved")%>
<strong><jsp:getProperty name="book"
property="title"/></strong>
<br> <br>
</font>
当包含上面的自定义tag的JSP被解释的时候,Web container同时为脚本变量和脚本变
量引用的对象生成代码,为了生成代码,Web container需要确定的信息有:
■ 变量名
■ 变量class
■ 变量是否引用一个新的或者已存在的对象
■ 变量的可用范围
有两种方法可以提供上面提到的信息:在TLD中指定variable element、在TLD中指定
tei-class并且定义一个提供tag其他信息的class。使用variable element的方法很简
单,但是不够灵活。
◆4.6.2.1 variable Element
variable Element有下面列出的子elements:
■ name-given,作为常数的变量名
■ name-from-attribute,变量名是一个属性的值,在JSP解释时确定。
name-given和name-from-attribute两者之间必须指定其中的一个,下面的子elements
是可选择的:
■ variable-class,变量的class名,java.lang.String是缺省值
■ declare,是否变量引用一个新的对象,缺省值是true
■ scope,变量定义的范围,缺省值是NESTED,下面的表格定义了变量的可用范围和设
定方法。
---------------------------------------------------
值 可用范围 方法
- - - - - - - - - - - - - - - - - - - - - - - - - -
NESTED 开始和结束tag之间 实现BodyTag,doInitBody()和doAfterBody(),
没有实现BodyTag,doStartTag()
- - - - - - - - - - - - - - - - - - - - - - - - - -
AT_BEGIN 从开始tag到页面结束 实现BodyTag,doInitBody()和doAfterBody(),
doEndTag(),没有实现BodyTag,doStartTag(),
doEndTag()
- - - - - - - - - - - - - - - - - - - - - - - - - -
AT_END 从结束tag到页面结束 doEndTag()
---------------------------------------------------
Struts bean:define tag的实现遵守JSP的规范 ver1.1,它需要用户定义一个
TagExtraInfo子类,JSP的规范 ver1.2添加了variable element,用户应该在
bean:define tag中这样定义variable element:
<tag>
<variable>
<name-from-attribute>id</name-from-attribute>
<variable-class>database.BookDetails</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag>
◆4.6.2.2 TagExtraInfo Class
用户通过继承javax.servlet.jsp.TagExtraInfo来实现一个子类,子类必须实现
getVariableInfo()方法,这个方法返回一个包含下列信息的VariableInfo类型的数组
:
■ 变量名
■ 变量class
■ 变量是否引用一个新的或者已存在的对象
■ 变量的可用范围
Web container传递一个叫data的参数给getVariableInfo()方法,data参数包含tag的
所有的属性的属性值,这些属性被VariableInfo对象使用,VariableInfo对象包含变量
的名字和Java class。Struts tag库使用DefineTei类为bean:define tag创建的脚本变
量提供信息。因为脚本变量的名字和Java class是通过tag的属性传递,所以它们可以
通过data.getAttributeString()方法返回,并且传递给VariableInfo的构造函数。为
了允许变量book在页面的余下部分使用,变量的scope被设置为AT_BEGIN,如下面的所
示:
public class DefineTei extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData data) {
String type = data.getAttributeString("type");
if (type == null)
type = "java.lang.Object";
return new VariableInfo[] {
new VariableInfo(data.getAttributeString("id"),
type,
true,
VariableInfo.AT_BEGIN)
};
}
}
脚本变量的TagExtraInfo的Java class名字必须在TLD的tag element的tei-class子
element中定义,因此对于DefineTei的定义应该是:
<tei-class>org.apache.struts.taglib.bean.DefineTagTei
</tei-class>
◆4.7 协作(cooperating)Tags
Tags之间通过共享对象相互作用,JSP技术支持两种类型的对象共享:第一种类型是共
享对象被命名然后保存到pageContext中(可以被JSP页面和tag handlers访问的隐含对
象),如果访问被其他tag命名和创建的对象,tag handlers使用
page.getAttribute(name,scope)方法。第二种类型是对象共享,一组嵌套tags的父tag
创建的对象对于所有的内嵌tag handlers都是可用的,这种类型的对象共享的好处是使
用私有命名空间为对象命名,这样就减少了潜在的命名冲突。为了访问父tag创建的对
象,tag handler必须通过静态的TagSupport.findAncestorWithClass(from,class)方
法得到包含的tag,或者使用TagSupport.getParent()方法。前一种方法是在嵌套模式
不能保证的情况下使用,一旦父tag返回,tag handler可以得到任何静态的和动态的创
建的对象,静态创建的对象是父tag的成员变量。Private的对象也可以被动态创建,这
些对象可以使用setValue()方法保存在tag handler中,也可以使用getValue()方法返
回。
下面的例子解释了一个tag的tag handler既支持命名方法,也支持private对象的方法
来共享对象。例子中查寻tag的handler检查了connection属性是否在doStartTag()中设
定,如果connection属性已经设定,handler从pageContext中得到connection对象,否
则,handler首先得到父tag的handler,然后从中返回connection对象:
public class QueryTag extends BodyTagSupport {
private String connectionId;
public int doStartTag() throws JspException {
String cid = getConnection();
if (cid != null) {
// there is a connection id, use it
connection =(Connection)pageContext.
getAttribute(cid);
} else {
ConnectionTag ancestorTag =
(ConnectionTag)findAncestorWithClass(this,
ConnectionTag.class);
if (ancestorTag == null) {
throw new JspTagException("A query without
a connection attribute must be nested
within a connection tag.");
}
connection = ancestorTag.getConnection();
}
}
}
实现查询tag的handler可以用下面的两种方法使用:
<tt:connection id="con01" ....> ... </tt:connection>
<tt:query id="balances" connection="con01">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</tt:query>
<tt:connection ...>
<x:query id="balances">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</x:query>
</tt:connection>
TLD中tag handler的定义必须指明connection属性是可选的:
<tag>
...
<attribute>
<name>connection</name>
<required>false</required>
</attribute>
</tag>