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

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>
  • 效果:


JSP第六篇【自定义标签之传统标签】的更多相关文章

  1. 2_Jsp标签_传统标签功能简介

    1传统标签接口关系:                                   2功能简介                                                   ...

  2. JSP自定义标签——传统标签

    同JSP标签一样,自定义标签主要用于移除JSP页面中的Java代码,可以看到我们在JSP中其实是禁止使用Java脚本的,任何要想通过Java代码实现的功能都必须以标签形式来处理,可以使用JSP标签,J ...

  3. JSP传统标签开发

    1.标签技术的API类继承关系 1).JspTag接口是所有自定义标签的父接口 该接口中没有任何属性和方法,只有两个直接子接口,Tag接口和SimpleTag接口,把实现Tag接口的自定义标签叫做传统 ...

  4. 传智播客JavaWeb day07、day08-自定义标签(传统标签和简单标签)、mvc设计模式、用户注册登录注销

    第七天的课程主要是讲了自定义标签.简单介绍了mvc设计模式.然后做了案例 1. 自定义标签 1.1 为什么要有自定义标签 前面所说的EL.JSTL等技术都是为了提高jsp的可读性.可维护性.方便性而取 ...

  5. javaweb学习总结(二十四)——jsp传统标签开发

    一.标签技术的API 1.1.标签技术的API类继承关系 二.标签API简单介绍 2.1.JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属 ...

  6. JSP第七篇【简单标签、应用、DynamicAttribute接口】

    为什么要用到简单标签? 上一篇博客中我已经讲解了传统标签,想要开发自定义标签,大多数情况下都要重写doStartTag(),doAfterBody()和doEndTag()方法,并且还要知道SKIP_ ...

  7. javaweb(二十四)——jsp传统标签开发

    一.标签技术的API 1.1.标签技术的API类继承关系 二.标签API简单介绍 2.1.JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属 ...

  8. JavaWeb学习记录(十九)——jstl自定义标签库之传统标签

    一.传统标签 (1)JSP引擎将遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法. public void setPageContext(PageCo ...

  9. jsp jsp传统标签开发

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

随机推荐

  1. vue小组件练习之文字居中对齐组件

    时隔多月,继续学习Vue,这次是一个组件的制作过程 先让我们来看一下组件的预期效果 上图为公司自营的一个微信商城的某一部分截图,可以看到红框内部分的文字多行与单行是居中对齐的,我们现在要做的就是使用V ...

  2. Struts2传参碰到的奇怪问题

    在使用Struts2框架,前端页面提交参数为日期时,Action中获取类型直接是Date 出现的问题是,局域网中无法访问,外网可正常访问. 把Action中Date类型换成String类型都可正常访问 ...

  3. Windows Firewall Setting

    If our web site hosted on web server canot be accessed by other computer by public network, one of t ...

  4. spring boot 登录注册 demo (三) -- 前后端传递

    前端页面通过thymeleaf渲染 <dependency> <groupId>org.springframework.boot</groupId> <art ...

  5. 9.19.1 反射构造Class对象

    构造Class对象的三种方式:         第一种方式:                     (1)对于系统的类,必须写全名!                     (2)该方式会将.Cla ...

  6. 50行Python代码构建小型区块链

    本文介绍了如何使用python构建一个小型的区块链技术,使用Python2实现,代码不到50行. Although some think blockchain is a solution waitin ...

  7. tensorflow笔记(五)之MNIST手写识别系列二

    tensorflow笔记(五)之MNIST手写识别系列二 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7455233.html ...

  8. codeforces 466d Increase Sequence

    D. Increase Sequence time limit per test 1 second memory limit per test 256 megabytes input standard ...

  9. summernote使用心得

    今日因项目需要所以使用了很有名气的富文本编辑器summernote,分享使用方法如下!填了一点坑,提供给需要的朋友借鉴! 直接转入我的前端网 地址: http://www.qdfuns.com/not ...

  10. js、JSP、servlet之间的传递小结

    @ JS 与 JSP :JSP无法直接获取JS的值,只能通过隐藏表单或者dom节点设置. JSP中设置隐藏表单input,或者设置任意一个隐藏或者不隐藏的节点比如div, 而JS就通过document ...