JSP里的自定义Tags(4)

  ◆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>&nbsp;<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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值