JSP第六篇【自定义标签之传统标签】
为什么要使用自定义标签?
JSTL标签库只提供了简单的输出等功能,没有实现任何的HTML代码封装,并且某些复杂类型转换,或者逻辑处理的时候,JSTL标签库完成不了,需要自定义标签!
编写自定义标签的步骤:
- 编写一个实现Tag接口的Java类【标签处理器类】
- 在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第六篇【自定义标签之传统标签】的更多相关文章
- 2_Jsp标签_传统标签功能简介
1传统标签接口关系: 2功能简介 ...
- JSP自定义标签——传统标签
同JSP标签一样,自定义标签主要用于移除JSP页面中的Java代码,可以看到我们在JSP中其实是禁止使用Java脚本的,任何要想通过Java代码实现的功能都必须以标签形式来处理,可以使用JSP标签,J ...
- JSP传统标签开发
1.标签技术的API类继承关系 1).JspTag接口是所有自定义标签的父接口 该接口中没有任何属性和方法,只有两个直接子接口,Tag接口和SimpleTag接口,把实现Tag接口的自定义标签叫做传统 ...
- 传智播客JavaWeb day07、day08-自定义标签(传统标签和简单标签)、mvc设计模式、用户注册登录注销
第七天的课程主要是讲了自定义标签.简单介绍了mvc设计模式.然后做了案例 1. 自定义标签 1.1 为什么要有自定义标签 前面所说的EL.JSTL等技术都是为了提高jsp的可读性.可维护性.方便性而取 ...
- javaweb学习总结(二十四)——jsp传统标签开发
一.标签技术的API 1.1.标签技术的API类继承关系 二.标签API简单介绍 2.1.JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属 ...
- JSP第七篇【简单标签、应用、DynamicAttribute接口】
为什么要用到简单标签? 上一篇博客中我已经讲解了传统标签,想要开发自定义标签,大多数情况下都要重写doStartTag(),doAfterBody()和doEndTag()方法,并且还要知道SKIP_ ...
- javaweb(二十四)——jsp传统标签开发
一.标签技术的API 1.1.标签技术的API类继承关系 二.标签API简单介绍 2.1.JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属 ...
- JavaWeb学习记录(十九)——jstl自定义标签库之传统标签
一.传统标签 (1)JSP引擎将遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法. public void setPageContext(PageCo ...
- jsp jsp传统标签开发
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
随机推荐
- 截取TableView生成图片
先看一下实例效果: 如果所示,这是一个在APP中截图,并调起微信客户端,发送给好友的例子,图片就是一个tableView的截图. 先实现一个小例子,如果tableVIew里面的内容,没有超过当前屏幕显 ...
- Fatal error: Class 'LearningPHP1\mysqli' not found
在php文件的头部使用了namespace 结果报错mysqli不在LearningPHP1中, 结论:如果定义了命名空间,在使用mysqli做连接的时候就要指明mysqli所在的命名空间. < ...
- Postman测试http接口
一.安装 官网:https://www.getpostman.com/ Postman是一个Chrome的一个插件工具,我们可以通过Chrome的应用商店进行进行搜索并安装,安装完成会在桌面上显示一个 ...
- 如何生成转储(dmp)文件--工具篇
转储文件也就是我们常说的dump文件.可以把转储文件看成软件的某个时刻的一个快照.转储文件一般都是在软件出现问题时手动生成或者程序自动生成.下面我们介绍几种生成转储文件的方法. 1. 任务管理器 任务 ...
- Centos7安装nginx并设置为HTTP代理服务器(正向代理)
# wget https://nginx.org/download/nginx-1.9.9.tar.gz # .tar.gz # cd nginx- # ./configure --prefix=/u ...
- .NET Core多平台开发体验[2]: Mac OS X
除了微软自家的Windows平台, .NET Core针对Mac OS以及各种Linux(RHEL.Ubuntu.Debian.Fedora.CentOS和SUSE等)都提供了很好的支持,我们先来体验 ...
- web安全普及:通俗易懂,如何让网站变得更安全?以实例来讲述网站入侵原理及防护。
本篇以我自己的网站[http://www.1996v.com]为例来通俗易懂的讲述如何防止网站被入侵,如何让网站更安全. 要想足够安全,首先得知道其中的道理. 本文例子通俗易懂,从"破解网站 ...
- adb 安装apk 报错:Failure [INSTALL_FAILED_CPU_ABI_INCOMPATIBLE]
这是因为系统里缺少了 Google Play 市场等各种谷歌服务应用,其实是因为版权问题,从 2.0 版本开始 Genymotion 提供的虚拟设备都已经移除了 Google Apps 以及 AR ...
- 搭建互联网DNS构架
author:JevonWei 版权声明:原创作品 构建"." DNS."com" DNS及"danran.com",用户通过"电 ...
- 八大排序算法---基于python
本文节选自:http://python.jobbole.com/82270/ 本文用Python实现了插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 1.插入排序 ...