博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JSP第六篇【自定义标签之传统标签】
阅读量:6581 次
发布时间:2019-06-24

本文共 11342 字,大约阅读时间需要 37 分钟。

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

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类【标签处理类】
1.0
zhongfucheng
/zhongfucheng
viewIp
tag.showIp
empty
复制代码
  • 下面我们来测试一下看能不能用


标签处理类详细说明

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

  • 首先我们来看一下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文件当前用到的内容吧
1.0
myshortname
http://mycompany.com
复制代码
  • 我们一个一个来看:
    • 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文件中描述一把
showMethod
tag.Demo1
empty
复制代码
  • 效果:

带属性的标签

上面我们编写的自定义标签都没有附带属性的,我们在使用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表达式
formatDate
tag.Demo1
empty
format
true
true
复制代码
  • 我们来看一下效果:


标签的继承关系

  • 在深入讲解之前,我们先来看一下各种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了
foreverEval
tag.Demo1
tagdependent
复制代码
  • 注意看横向的滑轮,已经死循环输出了:

  • 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文件:
BodyContentToLowerCase
tag.Demo1
tagdependent
复制代码
  • 效果:


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

转载地址:http://ddino.baihongyu.com/

你可能感兴趣的文章
openldap安装
查看>>
C++ istream::peek()
查看>>
[leetcode]count and say
查看>>
flex java socket通信
查看>>
比VC的TRACE还要好的调试命令
查看>>
回忆大学到现在为止学到了什么?
查看>>
润乾报表 - 缓存问题
查看>>
ecshop修改用户登录成功和退出成功的提示页面信息
查看>>
C Cheat Sheet
查看>>
log4j:ERROR Could not read configuration file [log4j.properties].
查看>>
图像处理经典图片Lena背后的故事
查看>>
NSData
查看>>
Design Pattern - 7原则
查看>>
[译] wxWidgets - wxDir
查看>>
gjrand 4.0.1 发布,伪随机数生成器
查看>>
每天一个linux命令(53):route命令
查看>>
使用反射获取Android中隐藏的方法
查看>>
PHP开发者常犯的10个MySQL错误
查看>>
计算图像相似度——《Python也可以》之一(转)
查看>>
Go编程语言规范2-类型
查看>>