欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tomcat与JavaWeb 8.1 自定义JSP标签及其主要接口、类介绍

程序员文章站 2022-05-30 21:25:45
...

1.    自定义标签简介

JSP标签包括以下几种形式。

  • 主体内容和属性都为空的标签,例如:<mm:hello/>
  • 包含属性的标签,例如: <mm:message key="hello.hi"/>
  • 包含主体内容的标签,例如<mm:greeting>How are you?</mm:greeting>。 其中<mm:greeting>称为标签的起始标志,</mm:greeting>称为标签的结束标志,两个标签之间的内容称为标签主体。
  • 包含属性和主体内容的标签,例如:<mm:greeting username="Tomcat">How are you? </mm:greeting>
  • 嵌套的标签,例如:<mm:greeting> <mm:user name="Tomcat" age="18" /> </mm:greeting>,其中外层标签<mm:greeting>称为父标签,内层标签<mm:user >称为子标签。

为了便于组织和管理标签,可以把一组功能相关的标签放在同一个标签库中。开发包含自定义标签的标签库包括以下步骤:

  1. 创建自定义标签的处理类(Tag Handler Class)。
  2. 创建TLD标签库描述文件。(Tag Library Discriptor)

假设甲方开发了可重用性比较高的标签库,那么除了甲方本身的Web应用可以使用它,其他方(如乙方)也可以使用它。本系列后续内容(JSTL Core标签库)会介绍如何在Web应用中使用第三方提供的JSP标准标签库。

本章将按照如下步骤在Web应用中使用标签库。

  1. 把标签处理类及相关类的.class文件放在WEB-INF/classes目录下。(如果使用IDEA进行开发,只需要在src目录下编写好对应的.java文件的代码,编译时会自动在对应位置生成.class)
  2. 把TLD标签库描述文件存放在WEB-INF目录或者其自定义的子目录下。
  3. 在web.xml中声明所引用的标签库。
  4. 在JSP文件中使用标签库中的标签。

本章下一节介绍的各个范例分别用于演示不同类型标签的创建及其使用方法。

  • 8.2.1节:带属性的message标签。
  • 8.2.2节:能重复执行标签主体内容的iterate标签。
  • 8.2.3节:能访问标签主体内容的greet标签。

2.    JSP Tag API

Servlet容器在运行JSP文件时,如果遇到自定义标签,就会调用这个标签的处理类(Tag Handler CLass)的相关方法。标签处理类可以继承JSP Tag API中的TagSupport类或BodyTagSupport类。

JSP Tag API位于javax.serlvet.jsp.tagext包中。

2.1    JspTag接口

所有的标签处理类都要实现JspTag接口。这个接口只是一个标识接口,没有任何方法,主要是作为Tag和SimpleTag接口的共同接口。在JSP2.0以前,所有的标签处理类都要实现Tag接口,实现该接口的标签称为传统标签(Classic Tag)。JSP2.0提供了SimpleTag接口,实现该接口的标签称为简单标签(Simple Tag)。本章将介绍传统标签的使用方法,后续的章节会介绍简单标签的用法。

2.2    Tag接口

Tag接口定义了所有传统标签处理类都要实现的基本方法,它包括以下几种:

  • setPageContext(PageContext pc):由servlet容器调用此方法,向当前标签处理对象即Tag对象传递当前的PageContext对象。
  • setParent(Tag t):由servlet容器调用该方法,向当前Tag对象传递父标签的Tag对象。
  • getParent():返回Tag类型的父标签的Tag对象。
  • release():当Servlet容器需要释放Tag对象所占用的资源时,会调用该方法。
  • doStartTag():当Servlet容器遇到标签的起始标志时,会调用此方法。doStartTag()方法返回一个整形值,用来决定程序的后续流程。它有两个可选值,即Tag.SKIP_BODY(=0)和Tag.EVAL_BODY_INCLUDE(=1)。前者表示标签之间的主体内容被忽略,后者表示标签之间的主体内容被正常执行。例如对于以下代码:
    <prefix:mytag>
    Hello World!
    .......
    .......
    </prefix:mytag>

    假若<mytag>标签的处理对象的doStartTag()方法返回Tag.SKIP_BODY,那么“Hello World!”字符串将不会显示在网页上。若返回Tag.EVAL_BODY_INCLUDE,那么“Hello World!”字符串将显示在网页。
  • doEndTag():当Servlet容器遇到标签的结束标志时,会调用此方法。doEndTag()方法也返回一个整数值,用来决定程序的后续流程,该整数值也有两个可选值,即Tag.SKIP_PAGE(=5)和Tag.EVAL_PAGE(=6)。前者表示立刻停止执行标签后面的JSP代码,网页上未处理的静态内容和Java程序片段均被忽略,任何已有的输出内容立刻返回到客户的浏览器上。后者则表示按正常的流程继续处理JSP文件。

标签处理类的对象(即Tag对象)由Servlet容器负责创建。容器在执行JSP文件时,如果遇到JSP文件中的自定义标签,就会寻找缓存中的相关的Tag对象,如果还不存在,就会创建一个Tag对象,把它存放在缓存中,以便下次处理自定义标签时重复使用。当Servlet容器得到了Tag对象后,会按照如图所示的流程调用Tag对象的相关方法。

  1. 容器调用Tag对象的setPageContext()和setParent()方法,把当前JSP页面的PageContext对象及父标签处理对象传给当前Tag对象。如果不存在父标签,则把父标签处理对象设为null。
  2. 容器调用Tag对象的一系列set方法,设置Tag对象的属性。如果标签没有属性,则无需这个步骤。
  3. 容器调用Tag对象的doStartTag()方法。
  4. 如果doStartTag方法返回Tag.SKIP_BODY,就不执行标签主体的内容;如返回Tag.EVAL_BODY_INCLUDE,就执行主体内容。
  5. 容器调用Tag对象的doEndTag()方法。
  6. 如果doStartTag方法返回Tag.SKIP_PAGE,就不执行标签的后续JSP代码;如返回Tag.EVAL_PAGE,就执行标签后续的JSP代码。

Tomcat与JavaWeb 8.1 自定义JSP标签及其主要接口、类介绍

一个Tag对象被创建后,就会一直存在,可以被Servlet容器重复调用。当Web应用终止时,容器就会先调用Web应用中的所有的Tag对象的releaser()方法,然后销毁这些Tag对象。

2.3    IterationTag接口

IterationTag接口继承自Tag接口,相比于Tag接口增加了重复执行标签主体内容的功能。

IterationTag接口定义了一个doAfterBody()方法,Servlet容器在执行标签主体内容后,会调用此方法。如果Servlet容器未执行标签主体内容,那么就不会调用此方法。doAfterBody()方法也返回一个整数值,用来决定程序后续流程。它有两个可选值:Tag.SKIP_BODY和IterationTag.EVAL_BODY_AGAIN。Tag.SKIP_BODY表示不再执行标签主体内容;后者则表示重复执行标签主体内容。IterationTag接口还定义了一个可作为doAfterBody()方法的返回值的int类型的静态常量:IterationTag.EVAL_BODY_AGAIN,用于指示Servlet容器重复执行标签主体内容。

Servlet容器JSP文件中的这种标签时,会寻找缓存中的相关IterationTag对象,如果还不存在,就创建一个IterationTag对象,把它存放在缓存中,以便下次处理自定义标签时重复使用。Servlet容器在得到了IterationTag对象后,会按照下面的流程来调用IterationTag对象的相关方法:

  1.  容器调用IterationTag对象的setPageContext()和setParent()方法,把当前JSP页面的PageContext对象以及父标签处理对象传给当前IterationTag对象,如果不存在父标签,则设为null。
  2. Servlet容器调用IterationTag对象的一系列set方法,同Tag的步骤2.
  3. 同Tag的步骤3,容器调用IterationTag的doStartTag()方法。
  4. 同Tag步骤4.
  5. 如果在步骤4中Servlet容器执行了标签主体的内容,那么就调用doAfterBody()方法。
  6. 如果doAfterBody()方法返回Tag.SKIP_BODY(0),就不再执行标签主体的内容;如果返回IterationTag.EVAL_BODY_AGAIN(=2),就继续重复执行标签主体的内容。
  7. 容器调用IterationTag的doEndTag()方法。
  8. 判断doEndTag()返回值,同Tag步骤6。
    Tomcat与JavaWeb 8.1 自定义JSP标签及其主要接口、类介绍
    可见,相比于Tag,IterationTag的处理流程增加了 doAfterBody()的步骤。

2.4    BodyTag接口

BodyTag接口又继承自IterationTag接口,增加了直接访问和操纵标签主体内容的功能。BodyTag接口定义了两个方法:

  • setBodyContent(BodyContent bc):容器通过此方法向BodyTag对象传递一个用于缓存标签主体的执行结果的BodyContent对象。
  • doInitBody():当容器调用完setBodyContent()方法之后,在第一次执行标签主体之前,先调用此方法,该放爱抚用于为执行标签主体做初始化工作。

只要符合以下条件之一,setBodyContent()和doInitBody()方法就不会被容器调用

  • 标签主体为空。
  • doStartTag()方法的返回值为Tag.SKIP_BODY或者Tag.EVAL_BODY_INCLUDE。

也就是说,只有同时符合以下两个条件,容器才会调用上述两个方法:

  • 标签主体不为空、
  • doStartTag()方法的返回值为BodyTag.EVAL_BODY_BUFFERED(= 2 )。

以上提到的BodyTag.EVAL_BODY_BUFFERED是在BodyTag接口中定义的int类型的静态常量,它可作为doStartTag()的返回值,指示容器调用BodyTag对象的setBodyContent()和doInitBody()方法。

Servlet容器在处理JSP文件中的这种标签时,会寻找缓存中的相关的BodyTag对象,如果还不存在就创建一个BodyTag对象并放在缓存中,以便重复使用。容器在得到BodyTag对象后,会按照以下流程调用BodyTag的相关方法。

  1. 同Tag和IterationTag的步骤1,容器调用setParent()和setPageContext()方法。
  2. 同Tag和IterationTag的步骤2,容器调用一系列set方法设置属性。
  3. 同Tag和IterationTag的步骤3,容器调用BodyTag的doStartTag()方法。
  4. 如果doStartTag方法返回Tag.SKIP_BODY,就不执行标签主体的内容;如返回Tag.EVAL_BODY_INCLUDE,就执行主体内容;如果doStartTag()方法返回BodyTag.EVAL_BODY_BUFFERED,就先调用setBodyContent()和doInitBody()方法,再执行标签主体的内容。
  5. 同IterationTag步骤5,如果在步骤4中Servlet容器执行了标签主体的内容,那么就调用doAfterBody()方法。
  6. 同IterationTag步骤6,如果doAfterBody()方法返回Tag.SKIP_BODY,就不再执行标签主体的内容;如果返回IterationTag.EVAL_BODY_AGAIN,就继续重复执行标签主体的内容。
  7. 同IterationTag步骤7,容器调用IterationTag的doEndTag()方法。
  8. 判断doEndTag()返回值,同Tag步骤6。
    Tomcat与JavaWeb 8.1 自定义JSP标签及其主要接口、类介绍
    可以看到,这个处理逻辑相比于IterationTag又增加了判断doStartTag()方法是否返回BodyTag.EVAL_BODY_BUFFERED,来决定是否调用setBodyContent()和doInitBody()方法的步骤。

2.5    TagSupport类和BodyTagSupport类

这两个类是标签实现类,其中TagSupport类实现了IterationTag接口,BodyTagSupport类则继承自TagSupport类,并且实现了BodyTag接口。用户自定义的标签处理类可以继承TagSupport类或BodyTagSupport类。下面是TagSupport类的部分代码:

package javax.servlet.jsp.tagext;

...

public class TagSupport implements IterationTag, Serializable {
    private static final long serialVersionUID = 1L;
    private Tag parent;
    private Hashtable<String, Object> values;
    protected String id;
    protected transient PageContext pageContext;

    public TagSupport() {
    }

    public int doStartTag() throws JspException {
        return 0;
    }

    public int doEndTag() throws JspException {
        return 6;
    }

    public int doAfterBody() throws JspException {
        return 0;
    }

    public void release() {
        this.parent = null;
        this.id = null;
        if (this.values != null) {
            this.values.clear();
        }

        this.values = null;
    }

    public void setParent(Tag t) {
        this.parent = t;
    }

    public Tag getParent() {
        return this.parent;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
    }

    public void setValue(String k, Object o) {
        if (this.values == null) {
            this.values = new Hashtable();
        }

        this.values.put(k, o);
    }

    public Object getValue(String k) {
        return this.values == null ? null : this.values.get(k);
    }

    public void removeValue(String k) {
        if (this.values != null) {
            this.values.remove(k);
        }

    }

    public Enumeration<String> getValues() {
        return this.values == null ? null : this.values.keys();
    }
}

    2.5.1    parent和pageContext变量

这是TagSupport类的两个重要的成员变量,其中parent是private访问级别,表示父标签的处理对象;pageContext是protected访问级别,代表当前JSP页面的PageContext对象。

Servlet容器在调用doStartTag()方法前,会先调用setPageContext()和setParent()方法,设置这两个成员变量,因此在TagSupport子类的doStartTag()或doEndTag()等方法中可以通过getParent()方法来获取父标签的处理对象。在TagSupport类中定义了protected访问级别的pageContext成员变量,因此在TagSupport子类的doStartTag()或doEndTag()等方法中可以直接访问pageContext变量。PageContext对象在标签处理类中大有用武之地,在本章及下一章的标签处理类的例子中都会用到。

在TagSupport的构造方法中不能访问pageContext成员变量,因为此时容器还没有调用setPageContext()方法对pageContext成员变量进行初始化。

    2.5.2    处理标签的方法

对于用户自定义的标签处理类,主要重新实现TagSupport类中的doStartTag()和doEndTag()方法,分别提供Servlet容器在遇到标签起始和结束标志时执行的操作。如果希望Servlet容器重复执行标签主体的内容,那么还可以重新实现TagSupport类的doAfterBody()方法。

    2.5.3    用户自定义的标签属性

如果在标签中包含自定义的属性。例如:

<prefix:mytag username="Tomcat">
...
...
</prefix:mytag>
那么在标签处理类中应该将这个username作为成员变量,并且分别提供设置和读取属性的方法。假定以上username为String 类型,可以定义如下方法:
private String username;
public void setUsername(String username){
    this.username = username;
}
public String getUsername(){
    return username;
}

Servlet容器在调用标签处理对象的doStartTag()方法前,会先调用以上setUsername(String username)方法,把标签中的username属性的值"Tomcat"赋值给标签处理对象的username成员变量,因此在标签处理类中必须提供serUsername(String username)方法,而username成员变量和getUsername()方法不是必须的,可以根据需求决定是否定义它们。


如果希望操控标签主体内容,可以让自定义的标签处理类继承BodyTagSupport类,下面是其部分源代码:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;

public class BodyTagSupport extends TagSupport implements BodyTag {
    private static final long serialVersionUID = -7235752615580319833L;
    protected transient BodyContent bodyContent;

    public BodyTagSupport() {
    }

    public int doStartTag() throws JspException {
        return 2;  //BodyTag.EVAL_BODY_BUFFERED
    }

    public int doEndTag() throws JspException {
        return super.doEndTag();
    }

    public void setBodyContent(BodyContent b) {
        this.bodyContent = b;
    }

    public void doInitBody() throws JspException {
    }

    public int doAfterBody() throws JspException {
        return 0;
    }

    public void release() {
        this.bodyContent = null;
        super.release();
    }

    public BodyContent getBodyContent() {
        return this.bodyContent;
    }

    public JspWriter getPreviousOut() {
        return this.bodyContent.getEnclosingWriter();
    }
}

BodyTagSupport类有个重要的bodyContent成员变量,它是protected访问级别的成员变量,为
javax.servlet.jsp.tagext.BodyContent类型,用于缓存标签主体的执行结果。

如果标签处理类的doStartTag()方法返回BodyTag.EVAL_BODY_BUFFERED,那么容器在执行标签主体之前会先创建一个用来缓存标签主体的执行结果的BodyContent对象,接着调用serBodyContent(BodyContent bc)方法,使得bodyContent成员变量引用BodyContent对象,然后再调用doInitBody()方法。Servlet容器在执行标签主体内容时,会把得到的执行结果缓存到BodyContent对象中。在标签处理类中可以直接访问bodyContent成员变量,也可以通过getBodyContent()方法得到该对象。BodyContent类的getString()方法返回字符串形式的标签主体的执行结果。