(十三)自定义JSTL标签

前面的博客,我们讲过了 自定义 el函数

讲一个 自定义标签技术 ;


目录


自定义标签

自定义标签 主要用于移除JSP页面中的java代码 ;

要使用自定义标签移除JSP页面中的java代码,只需要完成以下两个步骤:

  1. 编写一个实现Tag接口的Java类,把页面java代码移到这个java类中(标签处理器类
  2. 编写标签库描述符(tld)文件,在tld文件中把标签处理器描述成一个标签 ;(tld文件,一般放在WEB-INF下面

快速入门:使用标签输出客户机IP

分析自定义标签的执行流程

Tag接口的方法都是由服务器调用的,但是在调用dostartTag的时候,会先调用接口的setPageContext()方法,因此,我们可以得到pageContext对象;

自定义标签运行原理

当浏览器,访问web服务器的jsp页面时,遇到自定义标签;

  1. 首先实例化出一个自定义标签类的对象
  2. 利用Tag接口中的setPageContext方法,将页面的pageContext对象传递给处理器类的对象;
  3. 再调用setParent方法,如果标签有父标签,则将父标签对象作为参数,传递进去;如果没有父标签,则参数为null
  4. 上述工作完成以后,自定义标签类对象,相当于完成初始化操作了,这时候服务器开始执行标签,遇到开始标签,就执行doStartTag方法 ;
  5. 如果标签有标签体,服务器一般会执行标签体 ;
  6. 执行到标签结束符,就会调用doEndTag方法;
  7. 整个标签执行完毕以后,服务器一般会调用release方法,释放标签工作时锁占用的资源 ;

工作原理的源码
机器灵 砍菜刀

从源码,我们可以看出,release方法并没有得到执行,服务器把自定义标签对象,放到了缓存中,便于下次访问;

就像servlet对象一样,一直驻留在服务器内存中,直到服务器关闭 ;

其中tld文件,服务器会把WEB-INF下面的tld文件,全部加载到内存中,保存到一个Map集合中,其中键就是jsp文件中的uri

案列:使用标签输出客户机IP

我们新建一个java类,让它继承 TagSupport 类;重写我们需要的方法 ,这里我们仅仅需要重写 doStartTag 方法;

 public class ViewTag extends TagSupport {

//    @Override
    public int doStartTag() throws JspException {
//        利用pageContext对象,获取request
        HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
        String ip = request.getRemoteAddr() ;
        JspWriter out = this.pageContext.getOut() ;
        try {
            out.write(ip);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return super.doStartTag();
    }
}

然后我们需要写一个 TLD 文件 ;

如何编写TLD文件?!

笔者也不会写,但是笔者会抄啊! (具体怎么抄,看笔者博客 怎么抄tld文件(可以点击)

下面是我打开的 c.tld 的部分代码

我们怎么写 TLD 文件,就这些代码,稍微改吧改吧,就是我们自己的 TLD 文件了

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

  <description>JSTL 1.1 core library</description>
  <display-name>JSTL core</display-name>
  <tlib-version>1.1</tlib-version>
  <!-- 这里我们要改下,改成我们自己的简化名,比如笔者,改成了自己的名字缩写 -->
  <!--  <short-name>c</short-name> -->
  <short-name>yaz</short-name>
   <!-- 这里我们也要改下,改成我们自己的uri,比如笔者,改成了自己的网址,随便写一个也行 -->
  <!--  <uri>http://ijava.xin./jsp/jstl/core</uri> -->
  <uri>http://ijava.xin</uri>

<!--  这是一大堆描述信息,直接删掉就好了
  <validator>
    <description>
        Provides core validation features for JSTL tags.
    </description>
    <validator-class>
        org.apache.taglibs.standard.tlv.JstlCoreTLV
    </validator-class>
  </validator>
-->

<!-- 下面是一个例子,如何描述EL函数  -->
  <tag>
  <!-- 描述这个标签是干嘛的;我们直接删掉
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    -->
    <!-- 标签名字,如果你定义的标签有属性,就写;没属性就不需要写下面的那些 -->
    <name>catch</name>
     <!-- 标签方法来源的类,要写完整类名,留着 -->
    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
     <!-- 是否有标签体,没有的话就写empty,有的话,就写上标签体是啥,留着 -->
    <body-content>JSP</body-content>
     <!-- 标签属性,留着 -->
    <attribute>
     <!-- 还是描述信息,删掉删掉
        <description>
Name of the exported scoped variable for the
exception thrown from a nested action. The type of the
scoped variable is the type of the exception thrown.
        </description>
       -->
        <!-- 属性名字,留着 -->
        <name>var</name>
         <!-- 是否是必备属性,必备属性就写true,写了true的属性,就必须写上值了,留着 -->
        <required>false</required>
         <!-- 全称是 run time expression alue 翻译为 '运行时表达式',表示是否支持运行时赋值 ,留着 -->
         <!-- 如果是 true 就表示属性支持动态赋值 -->
        <rtexprvalue>false</rtexprvalue>
    </attribute>
  </tag>

</taglib>

在编写完毕 tld 文件,然后在JSP页面中引入我们的标签库,就可以用我们自定义的标签了 ;


关于标签处理器类的方法

这个类里面的方法。并不是向我们上面举的例子那么简单的;上面只是简单的重写了 doStartTag 方法;

下面,我讲下,什么时候该重写什么方法 ;

  • 获取标签体

    为了获取到标签体,我们需要覆写doStarTag()方法,改变其返回值为EVAL_BODY_BUFFERED;这样标签体就会被当做对象,保存在该标签器类对象中 ; 然后在doEndTag() 里面用 java 代码完成逻辑 ;

    实例:将标签体转成大写字母

    @Override
    public int doStartTag() throws JspException {
        return BodyTagSupport.EVAL_BODY_BUFFERED ;
    }
    
    @Override
    public int doEndTag() throws JspException {
    //        获取标签体
        BodyContent bodyContent = this.getBodyContent() ;
        String content = bodyContent.getString() ;
        content = content.toUpperCase() ;
        try {
            this.pageContext.getOut().write(content);
        } catch (IOException e) {
            throw new RuntimeException(e) ;
        }
        return super.doEndTag();
    }
  • doAfterBody()

    为了控制显示次数,需要利用到 doAfterBody() 这个方法在执行完标签体以后以及执行 doEndTag()方法之前得到调用 ;

    为了确保执行标签体,我们也还需要覆写 doStarTag()方法,改变其返回值;

    实例:控制标签体显示次数

    int i = 5 ;
    @Override
    public int doStartTag() throws JspException {
        return Tag.EVAL_BODY_INCLUDE;
    }
    
    @Override
    public int doAfterBody() throws JspException {
        if (i-- > 0 ){
            return TagSupport.EVAL_BODY_AGAIN;
        }else {
            i = 5 ;
            return TagSupport.SKIP_BODY ;
        }
    
    }
  • 控制标签体是否显示

    取决于d*oStarTag()*方法的方法值
    EVAL_BODY_INCLUDE 包含标签体的意思,会显示标签体 ;而 SKIP_BODY 跳过标签体

    @Override
    public int doStartTag() throws JspException {
        return Tag.SKIP_BODY;
    }
  • 是否显示页面

    主要是利用 doEndTag() 方法的返回值 ;
    SKIP_PAGE 代表后续的jsp页面的内容,不再进行处理,也就是不会显示;

    @Override
    public int doEndTag() throws JspException {
        return Tag.SKIP_PAGE;
    }

自定义标签功能扩展

  1. 控制JSP页面中某一部分的内容是否显示;

    利用是否显示标签体的技术,即可完成 ;

  2. 控制整个页面是否显示;

    做法:在整个页面的外部套个自定义标签,和上面一样 ;

  3. 控制重复输出页面内容

    重写 doAfterBody() 即可;

  4. 修改jsp页面内容输出 ;

    获取代表标签体的对象,对其进行改写,再利用 out 对象输出 ;


传统标签

上面我们学的自定义标签技术,属于 传统标签;需要实现的方法比较多,需要我们自己去记忆;

看下面的图, 左边的是JSP2.0的技术,实现不同的功能需要实现不同的接口;同一个接口中还要选择重写哪些方法

可以明显的看出来,传统标签 很烦 ,需要我们记住的东西太多了;说实话,笔者在这篇博客的时候,早就忘记了上面的那些知识,也是根据当年的笔记 ,写的这个博客 ;

Tag接口图


简单标签

现在我们学习 简单标签 ,让标签处理器类 继承 SimpleTagSupport 接口;

简单标签中最重要的两个方法:dotag()setJSPBody();

setJSPBody() 方法,在服务器执行标签的时候调用,将标签体作为对象传给自定义标签类对象;

doTag() 方法,服务器执行标签,都会调用这个方法;我们对标签的处理逻辑,都写在这个方法里面 ;


  • 是否显示标签体

    比如,我们想不显示标签体,那么,在这个方法中,就不做任何操作;反之,想显示标签体,就在方法中,获取标签体输出;

    @Override
    public void doTag() throws JspException, IOException {
        //只有doTag方法里面,什么都不做,就不会显示标签体
    }

  • 控制显示样式

    我们需要获取到标签体的字符串形式

    获取方法将标签体输送到StringWriter流的缓冲区中,从缓冲区中获取标签体的内容,从而修改样式

@Override
    public void doTag() throws JspException, IOException {
//       获取标签体
        JspFragment jspFragment = this.getJspBody();
//        将标签体输送到流的缓冲区中
        StringWriter stringWriter = new StringWriter();
//        这个方法很重要,负责将标签体输送到特定的流里面;
        jspFragment.invoke(stringWriter);
//        获取缓冲区中的内容
        String s = stringWriter.toString();
        s = s.toUpperCase();
        this.getJspContext().getOut().write(s);

    }

上面代码中最重要的一个方法:jspFragment.invoke(stringWriter);

参数用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数的值为 null,则将执行结果写入到 JspContext.getOut()方法返回的输出流 out 对象中(简而言之,可以理解为写给浏览器)


  • 是否显示页面

    不显示页面:在doTage方法中,抛出SkipPageException()异常,方法抛出这个异常,服务器,就不会再计算 剩下 的JSP页面了;

    @Override
    public void doTag() throws JspException, IOException {
        throw new SkipPageException() ;
    }

配置简单标签

配置简单标签的时候,配置标签体的时候,需要注意,不能写JSP了,改写为scriptlessSun公司的意思是:以后 脚本表达式 也不要出现在JSP里面了,统统用标签代替 ;

<tag>
        <name>....</name>
        <tag-class>xxxx.....</tag-class>
        <!-- 传统标签是写JSP,简单标签写 scriptless-->
        <body-content>scriptless</body-content>
    </tag>

与传统标签不同的是,简单标签执行结束以后,创建的对象,并不会像传统标签那样驻留在服务器内存中,而是被垃圾回收器回收掉 ;


开发带属性的标签

要想让一个自定义标签具有属性,通常需要完成两个任务:

  1. 在标签处理器中编写每个属性对应的setter方法
  2. tld文件中描述标签的属性

为自定义标签定义属性的时候,每个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收JSP页面调用自定义标签时传递进来的属性值。

例如,属性uri,在标签处理器类中就要定义相应的 setUri(String uri)方法。

在标签处理器中定义响应的set方法后,JSP引擎在解析执行开始标签,也就是doStarTag()方法前,会调用set属性方法,为标签设置属性;

原文地址:https://www.cnblogs.com/young-youth/p/11665714.html