jsp第六篇自定义标签之传统标签

Java3y      2022-02-17     374

关键词:

为什么要使用自定义标签?

JSTL标签库只提供了简单的输出等功能,没有实现任何的HTML代码封装,并且某些复杂类型转换,或者逻辑处理的时候,JSTL标签库完成不了,需要自定义标签!

编写自定义标签的步骤:

  1. 编写一个实现Tag接口的Java类【标签处理器类】
  2. 在WEB-INF目录下创建tld(Tag Library Descriptor)文件,在tld文件中对标签处理类(实现Tag接口的Java类)进行描述

快速入门

  • 目标:使用标签输出客户机的IP地址

  • 按照步骤来:首先编写一个实现Tag接口的Java类


    public class showIp implements Tag {
    
        @Override
        public void setPageContext(PageContext pageContext) {
    
        }
    
        @Override
        public void setParent(Tag tag) {
    
        }
    
        @Override
        public Tag getParent() {
            return null;
        }
    
        @Override
        public int doStartTag() throws JspException {
            return 0;
        }
    
        @Override
        public int doEndTag() throws JspException {
            return 0;
        }
    
        @Override
        public void release() {
    
        }
    }
  • 既然要获取到客户机的IP地址,那么request对象是必不可少的。现在问题来了,在Tag重写的方法好像不能直接获取到request对象啊
  • 经过我一番仔细的观察,发现了下面这个方法:
        @Override
        public void setPageContext(PageContext pageContext) {
    
        }
  • 既然能获取到pageContext对象,那么其他8大内置对象还不是随随便便?于是乎,我就定义一个成员变量pageContext,在setPageContext()方法中传递过来的pageContext赋值给我定义的成员变量即可

        private PageContext pageContext = null;

        @Override
        public void setPageContext(PageContext pageContext) {
            this.pageContext = pageContext;
        }
  • 好的,看回我们的需求:使用标签输出客户机的IP地址。在上面剩余5个方法中,最有可能就是在doStartTag()方法中编写代码


    @Override
    public int doStartTag() throws JspException {

        //获取到request对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();

        //获取到客户机的ip地址
        String ip = httpServletRequest.getRemoteAddr();
        
        //获取输出到浏览器的对象
        JspWriter jspWriter = pageContext.getOut();
        
        //下面的异常只能捕获,因为子类的异常不能比父类多
        try {
            jspWriter.write(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return 0;
    }
  • 接着,编写tld文件,描述实现Tag接口的Java类【标签处理类】

    <?xml version="1.0" encoding="ISO-8859-1"?>
    
    <taglib xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
            version="2.1">
    
        <tlib-version>1.0</tlib-version>
        <short-name>zhongfucheng</short-name>
        <uri>/zhongfucheng</uri>
    
        <!-- Invoke ‘Generate‘ action to add tags or functions -->
        <tag>
            <name>viewIp</name>
            <tag-class>tag.showIp</tag-class>
            <body-content>empty</body-content>
        </tag>
    
    
    </taglib>
  • 下面我们来测试一下看能不能用

技术分享图片


标签处理类详细说明

看完上面的程序,大部分人都是懵逼的。因为还不知道它具体是怎么用的,调用顺序是什么

  • 首先我们来看一下Tag接口的源码

    public interface Tag extends JspTag {
        int SKIP_BODY = 0;
        int EVAL_BODY_INCLUDE = 1;
        int SKIP_PAGE = 5;
        int EVAL_PAGE = 6;
    
        void setPageContext(PageContext var1);
    
        void setParent(Tag var1);
    
        Tag getParent();
    
        int doStartTag() throws JspException;
    
        int doEndTag() throws JspException;
    
        void release();
    }
  • 上面程序的执行流程:
    • JSP引擎遇到自定义标签,首先创建标签处理器类的实例对象
    • JSP引擎实例化完标签处理器类后,调用setPageContext()方法,将pageContext对象传递给标签处理器类,使得标签处理器类可以通过pageContext对象与JSP页面进行通信!
    • setPageContext()方法执行完后,调用setParent()方法,将当前标签的父标签传递给当前处理器类,如果当前标签没有父标签,则传入null
    • 当WEB容器执行到自定义标签的开始标记时,调用doStartTag()方法。
    • 当WEB容器执行到自定义标签的结束标记时,调用doEndTag()方法。
    • 一般来说,当WEB容器执行完自定义标签后,标签处理器类会驻留在内存中,直至停止WEB应用时,WEB容器才会调用release()方法

技术分享图片


  • 我们现在已经清楚了方法的执行顺序了,可Tag接口的源码还有4个变量阿,它们是用来做什么的呢?我们在编写JSP页面时,经常需要在页面中引入一些逻辑,例如:
    • 控制JSP页面某一部分(标签体)是否执行
    • 控制整个JSP页面是否执行
    • 控制JSP页面内容重复执行
    • 修改JSP页面内容输出
  • 再看回4个变量的名字,我们可以发现,这4个变量就是用来做逻辑判断的

  • 我们来测试一下吧,在doEndTag()方法中,返回的是SKIP_PAGE变量,看下会怎么样


    @Override
    public int doEndTag() throws JspException {
        return SKIP_PAGE;
    }
  • 我们再来看一看效果:

技术分享图片

  • 好像是没什么区别!我们再查看一下源代码,发现执行完标签后,后面的代码全都没有执行!

技术分享图片

  • doStartTag()方法使用的是SKIP_BODY和EVAL_BODY_INCLUDE这两个变量,判断是否执行标签体的内容。
  • doEndTag()方法使用的是SKIP_PAGE和EVAL_PAGE这两个变量,判断是否执行剩下页面的内容
  • 控制JSP页面内容重复执行和修改JSP页面内容输出后面会有!

tld文件详细说明

技术分享图片

技术分享图片

  • 首先我们来看一下tld文件当前用到的内容吧

    <tlib-version>1.0</tlib-version>
    <short-name>myshortname</short-name>
    <uri>http://mycompany.com</uri>
    
    <tag>
        <name></name>
        <tag-class></tag-class>
        <body-content></body-content>
    </tag>
  • 我们一个一个来看:
    • shortname推荐使用prefix
    • uri就是引入这个标签库使用的uri
    • name为标签名
    • tagclass为实现类
    • bodycontent为标签体的限制,它有4个值: EMPTY【不允许有标签体】,JSP【允许有JSP代码】 ,scriptless【不允许有脚本代码(也就是<%%>),允许有EL表达式,文本,JSP行为】 , tagdepentend【标签体内的JSP代码不会被解析,直接输出文本】

TagSupport类

大部分时候我们都不需要实现Tag接口来编写自定义标签,TagSupport是Tag的一个模板类,实现了pageContext,parent的getter、setter方法以及一些其他的功能。我们要做的就是重写doStartTag()和doEndTag()方法

  • 下面我们就来简单使用一下吧:

  • 继承TagSupport类,重写doStartTag()方法,比直接实现Tag接口简洁很多!


    public class Demo1 extends TagSupport {
    
        @Override
        public int doStartTag() throws JspException {
    
            //获取到request对象
            HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();
    
            String method = httpServletRequest.getMethod();
    
            JspWriter jspWriter = pageContext.getOut();
    
            try {
                jspWriter.write(method);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return 0;
        }
    }
  • 在tld文件中描述一把


    <tag>
        <name>showMethod</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>empty</body-content>
    </tag>
  • 效果:

技术分享图片

带属性的标签

上面我们编写的自定义标签都没有附带属性的,我们在使用core标签库的时候,标签一般都带有属性

其实JSTL标签库的原理就是自定义标签,把自定义标签搞明白了,对JSTL标签库的使用就有更好的理解了

  • 想要自定义标签带有属性也非常简单,只要在标签处理器类上加一个成员变量和setter、getter(),再在tld文件中描述下该属性即可!它的原理是这样的:当标签使用到属性的时候,引擎就会调用它的setter()方法

  • 下面我想要完成的功能是:使用标签的人,传入一个字符串格式就可以显示想要的格式日期

  • 编写标签处理器类,增加一个成员变量以及对应的setter、getter方法


    public class Demo1 extends TagSupport {
    
    
        //创建成员对象,对应的setter、getter方法
        private String format = null;
    
    
        @Override
        public int doStartTag() throws JspException {
    
            //创建日期格式化对象
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
    
            //格式化日期并向浏览器输出
            try {
                pageContext.getOut().write(simpleDateFormat.format(new Date()));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return 0;
        }
    
        public String getFormat() {
            return format;
        }
    
        public void setFormat(String format) {
            this.format = format;
        }
    }
  • 在tld文件中描述标签和属性,name代表的是属性的名字,required代表的是是否为必须,rtexprvalue代表能否使用EL表达式

    <tag>
        <name>formatDate</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>format</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
  • 我们来看一下效果:

技术分享图片

技术分享图片


标签的继承关系

  • 在深入讲解之前,我们先来看一下各种Tag接口、类之间的关系,这样学习下去才不会晕

技术分享图片

IterationTag说明

  • 我们已经使用过了Tag接口和TagSupport类了。接下来我们看一下IterationTag是什么玩意。

    public interface IterationTag extends Tag {
        int EVAL_BODY_AGAIN = 2;
    
        int doAfterBody() throws JspException;
    }
  • 从关系图我们也可以看出,IterationTag接口实现了Tag接口,InterationTag接口和Tag接口最主要的区别就是多了个doAfterBody()方法和EVAL_BODY_AGAIN变量
  • 理解起来也很简单:当doAfterBody()返回的是EVAL_BODY_AGAIN变量,那么标签体的内容就一直循环!当然了,TagSupport也实现了Iteration接口,也就是说TagSupport类也能完成Iteration接口的事情
  • 我们来使用一下吧:

    public class Demo1 extends TagSupport {
    
        @Override
        public int doStartTag() throws JspException {
    
            try {
                pageContext.getOut().write("hello");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            //执行标签体
            return EVAL_BODY_INCLUDE;
        }
    
        @Override
        public int doAfterBody() throws JspException {
    
            //标签体不断循环,直到doAfterBody()返回的是SKIP_BODY
            return EVAL_BODY_AGAIN;
        
        }
    }
  • tld文件中描述,既然标签体有内容,就不能用empty了

    <tag>
        <name>foreverEval</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>tagdependent</body-content>
    </tag>
  • 注意看横向的滑轮,已经死循环输出了:

技术分享图片

  • doAfterBody()中只要返回的是SKPI_BODY就退出循环,执行doEndTag()方法

        //定义一个变量,规定标签体循环的次数
        int x = 0;
        
        @Override
        public int doStartTag() throws JspException {
    
            try {
                pageContext.getOut().write("hello");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            //执行标签体
            return EVAL_BODY_INCLUDE;
        }
    
        @Override
        public int doAfterBody() throws JspException {
            
            x++;
            if (x >= 10) {
                return SKIP_BODY;
            }
    
            //标签体不断循环,直到doAfterBody()返回的是SKIP_BODY
            return EVAL_BODY_AGAIN;
    
        }
  • 现在我们已经能控制循环的次数了

技术分享图片


BodyTag说明

前面我们已经使用到了带标签体的自定义标签了,前面的都是只能直接输出而得不到标签体的内容,既然得不到标签体的内容,就更别说修改标签体了

  • 此时,我们就需要BodyTag接口的支持了!它专门用来处理带标签体的标签,下面我们来看一下BodyTag的源码

    public interface BodyTag extends IterationTag {
        /** @deprecated */
        int EVAL_BODY_TAG = 2;

        int EVAL_BODY_BUFFERED = 2;
    
        void setBodyContent(BodyContent var1);
    
        void doInitBody() throws JspException;
    }
  • BodyTag多了EVAL_BODY_BUFFERED变量【一个已经标识过时了】,多了setBodyContent和doInitBody()两个方法
  • 其实使用BodyTag十分简单
    • 如果doStartTag()方法返回的是EVAL_BODY_BUFFERED,把标签体的内容缓存起来
    • 接着调用setBodyContent()方法和doInitBody()方法,封装标签体的内容到BodyContent对象中
    • 接着调用doEndTag()方法
    • 对于标签体的内容,我们可以通过getBodyContenet()来获取!
  • 再看回上面的关系图,BodyTag实现了IterationTag和Tag接口,如果直接实现BodyTag接口做开发,要实现的方法就太多了。一般我们使用继承BodyTag的BodyTagSupport来做开发

BodyTagSupport说明

  • 首先来看一下源代码吧:


    public class BodyTagSupport extends TagSupport implements BodyTag {
        protected BodyContent bodyContent;
    
        public BodyTagSupport() {
        }
    
        public int doStartTag() throws JspException {
            return 2;
        }
    
        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直接定义为成员变量,在获取标签体内容的时候就不需要通过getBodyContent()获取了
    • 提供获取JspWriter的方法,不需要从pageConext中获取了
    • 以上的两个扩充都简化了我们的代码书写


        protected BodyContent bodyContent;

        public JspWriter getPreviousOut() {
            return this.bodyContent.getEnclosingWriter();
        }
  • 从BodyTag接口中,我就说到了:标签体的内容封装到了BodyContent类中,那么BodyContent类究竟是什么?我们来看一下源码

    public abstract class BodyContent extends JspWriter {
        private JspWriter enclosingWriter;
    
        protected BodyContent(JspWriter e) {
            super(-2, false);
            this.enclosingWriter = e;
        }
    
        public void flush() throws IOException {
            throw new IOException("Illegal to flush within a custom tag");
        }
    
        public void clearBody() {
            try {
                this.clear();
            } catch (IOException var2) {
                throw new Error("internal error!;");
            }
        }
    
        public abstract Reader getReader();
    
        public abstract String getString();
    
        public abstract void writeOut(Writer var1) throws IOException;
    
        public JspWriter getEnclosingWriter() {
            return this.enclosingWriter;
        }
    }
  • 原来BodyContent继承着JspWriter,它与JspWriter最大的区别是:BodyContent类的任何写入的内容并不自动地向页面输出!

  • 我们一般使用BodyContent都使用两个方法:


    //将数据转变成Reader对象
    public abstract Reader getReader();

    //将数据转变成String对象
    public abstract String getString();
  • 再从关系图我们可以看初,BodyTagSupport继承了TagSupport类实现了BodyTag接口,可以说:BodyTagSupport有着前面讲的接口和类的所有功能!

  • 下面我们来使用下BodyTagSupport将标签体的内容转成是小写的

  • 标签处理器类


    public class Demo1 extends BodyTagSupport {
    
        @Override
        public int doStartTag() throws JspException {
    
    
            //想要获取到标签体的内容,就要返回EVAL_BODY_BUFFERED变量
    
            return EVAL_BODY_BUFFERED;
    
        }
    
        @Override
        public int doEndTag() throws JspException {
    
            //获取到标签体的内容
            String value = bodyContent.getString();
    
            //将标签体的内容转成小写并输出
            try {
                this.getPreviousOut().write(value.toLowerCase());
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return super.doEndTag();
        }
    
    }
  • tld文件:

    <tag>
        <name>BodyContentToLowerCase</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>tagdependent</body-content>
    </tag>
  • 效果:

技术分享图片


如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y.

juc-共享模型之工具-第六篇(代码片段)

六、共享模型之工具1.线程池1.1自定义线程池步骤1:自定义拒绝策略接口@FunctionalInterface//拒绝策略interfaceRejectPolicy<T>voidreject(BlockingQueue<T>queue,Ttask);步骤2:自定义任务队列classBlockingQueue<T>//1.任务队列privateDeque< 查看详情

构建之法—第六篇

这周的学习关于典型用户和场景。VisualStudio是一个非常成功的软件开发集成环境,支持项目管理,测试工具,以及第三方的插件。对于定义典型用户,我们有不同的需求:受欢迎的典型用户和不受欢迎的典型用户,其中,受欢迎... 查看详情

自定义标签

...et.jsp.tagext.JspTag,它是所有的标签的根接口。在jsp2.0以后,定义了一个更加简单的javax.servlet.jsp.tagext.SimpleTag,这个接口就描述了如何自定义标签。标签的分类:传统标签简单标签SimpleTagSimpl 查看详情

自定义标签库开发(简单标签开发)

...术的推广,SUN公司为降低标签技术的学习难度,在JSP2.0中定义了一个更为简单、便于编写和调用的Sim 查看详情

(二十七)jsp标签之核心标签

...   JSTL标签库的使用是为弥补html标签的不足,规范自定义标签的使用而诞生的。使用JSLT标签的目的就是不希望在jsp页面中出现java逻辑代码。二、JSTL分类核心标签(用得最多)国际化标签(I18N格式化标签)数据库标签(SQL标签,很... 查看详情

javaweb开发之jstl标签库的使用自己定义el函数自己定义标签(带属性的带标签体的)

JSTL JSTL简单介绍:JSTL的全称:JSPStandardTagLibrary。JSP标准标签库JSTL的作用: 提供给JavaWeb开发者一个标准通用的标签函数库 和EL来代替传统直接在页面上嵌入Java程序(Scripting)的做法,以提高程序可读性、维护性和方... 查看详情

构建之法——第六篇

经过一段时间的学习,我已学习到《构建之法》的第八章需求分析。本周我将继续学习第九章项目经理、第十章典型用户和场景、第十一章软件设计与实现。第九章项目经理主要给我们介绍的是在企业里占据重要位置的项目经理... 查看详情

08.十分钟学会jsp传统标签编程

一、认识标签1,说明:传统标签编程在开发中基本用不到,学习标签编程主要还是为了完善知识体系。2,标签的主要作用:移除或减少jsp中的java代码3,标签的主要组成部分及运行原理4,简单标签示例:继承javax.servlet.jsp.tagext.TagSuppo... 查看详情

测开之函数进阶篇・第六篇《闭包》(代码片段)

目录闭包闭包的概念闭包的内存分配机制闭包闭包的概念一个完整的闭包,必须满足以下三个条件:1、函数中嵌套函数2、外层函数返回内存嵌套函数名3、内存嵌套函数有引用外层的一个非全局变量deffunc():num=100defcount... 查看详情

easyclickhtmlui第六节表单标签

EasyClickHtmlUI第六节表单标签表单的介绍表单用于搜集不同类型的用户输入(用户输入的数据),然后可以把用户数据提交到web服务器。表单相关标签的使用<form>标签表示表单标签,定义整体的表单区域<label>标签表... 查看详情

第六篇:元数据管理之“灵魂”三问

元数据管理作为企业数据治理的基础工作,贯穿数据产生、加工和使用的全生命周期,是有效管理和使用数据的基础和前提,通过元数据管理可以实现数据的可见、可懂、可用,帮助业务快速获取所需数据。本文... 查看详情

第六篇:元数据管理之“灵魂”三问

元数据管理作为企业数据治理的基础工作,贯穿数据产生、加工和使用的全生命周期,是有效管理和使用数据的基础和前提,通过元数据管理可以实现数据的可见、可懂、可用,帮助业务快速获取所需数据。本文... 查看详情

第六篇avplayer地址视频播放控件

1.引用头文件#importAVFoundation2.自定义AVPlayer(播放的机器)3.自定义AVPlayerItem(胶片)>>视频的URL转成AVAsset4.AVPlayerLayer(白板)  查看详情

构建之法阅读笔记第六篇—代码复审

  金无足赤,人无完人,这句话同样适用于代码,任何刚写出来的代码,都不可能是非常完美的,就像人一样,每个人都有这样或那样的缺点,需要我们自己或求助他人帮我们找出自身的缺点和不足,并加以改正,让自己... 查看详情

接口测试(java+testng+ant+jenkins)第六篇testng二

1、testng中,多次执行用例  @Test(dataProvider="data-provider")  在@Test标签后面加上参数来源:dataProvider(data-provider)   data-provider中有多少组数据,@Test就会使用数据执行多少次2、dataProvider的配置和使用@DataProvider(name="data-p... 查看详情

jsp自定义标签

谁能通俗的介绍下什么是JSp自定义标签还有在定义标签实现累的时候一定有SET方法voidset_(--)this._=_这方法是干吗的我觉得上楼回答的不完全对,自定义标签,不一定就是tomcat给封装好了的,自己也可以实现接口,编写*.tld(标签... 查看详情

web前端第六篇javascript对象

在JavaScript中除了null和undefined以外其他的数据类型都被定义成了对象,也可以用创建对象的方法定义变量,String、Math、Array、Date、RegExp都是JavaScript中重要的内置对象,在JavaScript程序大多数功能都是基于对象实现的<scriptlanguage=... 查看详情

jsp之常用标签库之初见

...示出来的就是比jsp原型语言写出来的简单清晰。2.JSTL四大标签分类有哪四大?答:核心标签、格式化标签、SQL标签、XML标签,其中最重要的是核心标签,其他的地位次之3.el标签具体的表示,即该如何在js 查看详情