• JSP第六篇【自定义标签之传统标签】


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

    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.

  • 相关阅读:
    Knockout应用开发指南 第八章:简单应用举例(2)
    微软ASP.NET站点部署指南(7):生产环境部署
    Knockout应用开发指南 第七章:Mapping插件
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(6)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(5)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(3)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(9)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(8)
    Microsoft Visual Studio .NET 2003 引导程序插件下载地址(非官方)
    Vs2010在没有安装SQL Server 2005/2008 Express时如何连接MDF数据文件?
  • 原文地址:https://www.cnblogs.com/Java3y/p/8424276.html
Copyright © 2020-2023  润新知