转载来源
http://blog.youkuaiyun.com/jiangwei0910410003/article/details/23915373
今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面就来看一下自定义标签的简介:
自定义标签主要用于移除Jsp页面中的java代码。
移除jsp页面中的java代码,只需要完成两个步骤:
编写一个实现Tag接口的Java类,并覆盖doStartTag方法,把jsp页面中的java代码写到doStartTag方法中。
编写标签库描述符(tld)文件,在tld文件中对自定义标签进行描述。
完成以上操作,即可在JSP页面中导入和使用自定义标签。
快速入门:使用自定义标签输出客户机IP
查看tag接口api文档,分析自定义标签的执行流程。
下面来看一下一个简单的Demo使用自定义标签打印客户机的IP地址
首先我们自定义标签类:ViewIpTag
package com.weijia.traditionaltag;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 自定义标签,然后将这个标签映射到这个类:mytag:viewIP
* 记得将自定义的标签绑定到一个url上面,这个url一般是公司的网址
*
*/
public class ViewIpTag extends TagSupport{
private static final long serialVersionUID = 1L;
@Override
public int doStartTag() throws JspException {
//内置一个pageContext对象,我们之前说到pageContext对象,它里面是封装了9个隐式对象
HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest();
JspWriter out = this.pageContext.getOut();
String ip = request.getRemoteAddr();
try {
out.print(ip);
} catch (IOException e) {
throw new RuntimeException(e);
}
return super.doStartTag();
}
}
自定义tld文件,mytag.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>JSTL 1.1 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.1</tlib-version>
<short-name>weijia</short-name>
<uri>http://www.weijia.cn/mytag</uri>
<!-- 显示IP地址 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>viewIP</name>
<tag-class>com.weijia.traditionaltag.ViewIpTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
这里我们将就自定义的标签类就注册好了,下面解释一下这些字段的含义:
首先看一下:
short-name:这个标签是指定我们定义标签的简称,这个作用不大
uri:这个标签是给这个标签文件指定一个访问路径,这个路径我们在Jsp页面中引入这个标签的时候需要用到
tag-class:这个标签就是指定我们自定义的标签类的全称
body-content这个:标签表明自定义标签是否有标签体内容(empty:没有,JSP:有)
我们注册之后标签类了,下面就在Jsp页面中进行使用了,这时候就要用到我们之前说到的Jsp的指令中的taglib了,格式如下:
<%@ taglib uri="http://www.weijia.cn/mytag" prefix="mytag" %>
这个就将我们定义的标签引入到Jsp页面中了,其中我们uri属性的值就是我们在标签定义文件mytag.tld中指定的那个uri那个标签值,当然这里的uri也可以直接指定mytag.tld文件的路径即:/WEB-INF/mytag.tld 也是可以的,其实我们查看翻译之后的Jsp代码可以看到,不管用那种方式,他其实加载的时候都是去找真是路径中文件:
其中prefix属性的值是标签前缀名,这个名称就是我们在Jsp页面中使用的标签前缀,这个值一般和tld文件的文件名是保持一致的
下面就是在Jsp中使用标签:
客户机的IP地址是:<mytag:viewIP/>
这样就是打印了客户机的IP地址,这里我们在Jsp页面中就没有Java代码了
上面我们介绍了一个简单的例子,下面我们来详细看一下这个自定义标签的执行原理:
JSP引擎将遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。
1、public void setPageContext(PageContext pc), JSP引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。
2、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
3、public int doStartTag(),调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法。
4、public int doEndTag(),WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法。
5、public void release(),通常WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至停止web应用时,web容器才会调用release方法。
我可以查看我们上面的例子翻译后的Jsp代码:
out.write("<body> \r\n");
out.write("\t<!-- 显示客户机的IP地址 -->\r\n");
out.write("\t客户机的IP地址是:");
if (_jspx_meth_mytag_005fviewIP_005f0(_jspx_page_context))
return;
out.write("\r\n");
out.write("\t\r\n");
我们可以看到,首先这个方法接收的是一个pageContext变量对象,这个和我们之前说的一样,自定义标签类中有一个pageContext变量对象就可以操作其他对象了,下面来看一下那个方法的代码,首先他会去加载那个标签类,同时注意到首先是执行setPageContext()方法的,将pageContext变量传递到标签类中,然后看setParent()方法传递的是null,因为我们打印IP的标签没有父标签的,接下来执行doStartTag()方法,然后再执行doEndTag()方法,这里我们看到是做个判断,如果doEndTag方法返回的值是Tag.SKIP_PAGE的话,就是说余下的jsp页面不执行了,所以返回一个true,那么我们看到上面的if判断代码中,如果这个方法返回true的话,直接return,下面的代码就不执行了。大体流程就是这样的。
下面我们用自定义标签来实现一些特定需求的功能:
1、不执行标签体内容
自定义标签类:
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 是否输出标签体内容
* @author weijiang204321
*
*/
public class TagDemo1 extends TagSupport{
@Override
public int doStartTag() throws JspException {
return TagSupport.EVAL_BODY_INCLUDE;//输出标签体内容
//return TagSupport.SKIP_BODY;//不输出标签体内容
}
}
我们看到只要doStartTag方法返回TagSupport.EVAL_BODY_INCLUDE常量,就会执行标签体内容,如果返回的是TagSupport.SKIP_BODY常量,就不会执行标签体内容,代码很简单。
下面我们再来注册这个标签类:
<!-- 是否显示标签体 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo1</name>
<tag-class>com.weijia.traditionaltag.TagDemo1</tag-class>
<body-content>JSP</body-content>
</tag>
因为是有标签体内容的,所以标签的值是JSP
在Jsp页面中使用:
<!-- 不执行标签体 -->
<simpletag:demo1>
aaaa
</simpletag:demo1>
2、控制JSP余下页面的内容不执行
自定义标签类:
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 控制整个JSP是否输出
* @author weijiang204321
*
*/
public class TagDemo2 extends TagSupport{
@Override
public int doStartTag() throws JspException {
return super.doStartTag();
}
@Override
public int doEndTag() throws JspException {
return TagSupport.EVAL_PAGE;
//return TagSupport.SKIP_PAGE;不执行余下的jsp内容
}
}
当doEndTag方法返回的是TagSupport.EVAL_PAGE常量的话就执行jsp余下的内容,如果返回的是TagSupport.SKIP_PAGE常量的话就不执行jsp余下的内容
<!-- 控制是否显示jsp页面 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo2</name>
<tag-class>com.weijia.traditionaltag.TagDemo2</tag-class>
<body-content>empty</body-content>
</tag>
在JSP页面中使用:
<!-- 不执行余下的页面内容 -->
<simpletag:demo2/>
这样使用之后,在这个标签之后的内容就不会执行了,我们在上面分析源代码的时候已经解析过了。页面都不会含有余下的内容了,如果我们将这个标签放在页面的第一行,那么这个页面就是一片空白,我们在浏览器中查看页面的源代码,也是发现一片空白的,因为out对象没有进行print了
3、重复执行标签体内容
自定义标签体类:
package com.weijia.traditionaltag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* 控制标签体重复执行
* @author weijiang204321
*
*/
public class TagDemo3 extends TagSupport{
private int count = 5;
@Override
public int doStartTag() throws JspException {
return TagSupport.EVAL_BODY_INCLUDE;
}
@Override
public int doAfterBody() throws JspException {
count--;
if(count > 0){
return TagSupport.EVAL_BODY_AGAIN;//执行完之后接着执行doAfterBody()方法
}else{
return TagSupport.SKIP_BODY;
}
}
@Override
public int doEndTag() throws JspException {
return TagSupport.SKIP_BODY;
}
}
这里我们需要在doAfterBody方法中操作了,因为这个方法的返回值为TagSupport.EVAL_BODY_AGAIN常量的话,这个方法还会被调用,直到这个方法返回TagSupport.SKIP_BODY,所以我们这里控制标签体内容执行5次,我们定义一个变量就可以了,然后控制doAfterBody方法的返回值,这里还要注意的是,在doStartTag方法中返回值是TagSupport.EVAL_BODY_INCLUDE常量,因为我们要执行标签体内容的。
注册自定义标签类:
<!-- 控制标签体重复输出 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo3</name>
<tag-class>com.weijia.traditionaltag.TagDemo3</tag-class>
<body-content>JSP</body-content>
</tag>
在Jsp页面中使用:
<!-- 重复执行标签体内容 -->
<simpletag:demo3>
aaaa
</simpletag:demo3>
这时候在页面中就会输出5个aaaa
4、修改标签体内容
自定义标签类:
package com.weijia.traditionaltag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
/**
* 修改标签体内容
* @author weijiang204321
*
*/
public class TagDemo4 extends BodyTagSupport{
@Override
public int doEndTag() throws JspException {
BodyContent bc = this.getBodyContent();//获取标签体内容对象
String content = bc.getString();
content = content.toUpperCase();//将标签体内容转成大写
try {
this.pageContext.getOut().write(content);//在将转化之后的内容输出到浏览器中
} catch (IOException e) {
throw new RuntimeException(e);
}
return BodyTagSupport.EVAL_BODY_INCLUDE;
}
@Override
public int doStartTag() throws JspException {
return BodyTagSupport.EVAL_BODY_BUFFERED;//这里返回缓存标签体内容常量
}
}
这里我们要注意的是,我们继承的是BodyTagSupport类了,要在doStartTag方法中返回BodyTagSupport.EVAL_BODY_BUFFERED常量,才可以取出标签体内容缓存,然后再doEndTag方法中取出标签体内容然后进行操作之后再写到浏览器中。
注册我们的自定义标签类:
<!-- 修改标签体内容 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo4</name>
<tag-class>com.weijia.traditionaltag.TagDemo4</tag-class>
<body-content>JSP</body-content>
</tag>
在Jsp页面中使用:
<!-- 修改标签体内容 -->
<simpletag:demo4>
bbbb
</simpletag:demo4>
这时候在浏览器中输出的是:BBBB
上面说到的是Jsp2.0以前的自定义标签的方法,从Jsp2.0以后,我们就开始使用了简单标签类SimpleTagSupport,因为我们可以看到Jsp2.0之前的是传统标签类的话,要想实现不同的功能,还需要继承不同的类,比如:TagSupport,BodyTagSupport,这样会增加开发成本,所以Jsp2.0之后引入了简单标签类SimpleTagSupport了,那么下面我们先来看一下简单标签的执行流程:
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。实现SimpleTag接口的标签通常称为简单标签。简单标签共定义了5个方法:
setJspContext方法
setParent和getParent方法
setJspBody方法
doTag方法
下面来看一下这些方法的解释
setJspContext方法
用于把JSP页面的pageContext对象传递给标签处理器对象
setParent方法
用于把父标签处理器对象传递给当前标签处理器对象
getParent方法
用于获得当前标签的父标签处理器对象
setJspBody方法
用于把代表标签体的JspFragment对象传递给标签处理器对象
doTag方法
用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
当web容器开始执行标签时,会调用如下方法完成标签的初始化
WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
如果简单标签有标签体,容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来。
执行标签时:
容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段,这段JSP片段中不能包含JSP脚本元素。
WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,如下所示:
getJspContext方法
用于返回代表调用页面的JspContext对象.
public abstract void invoke(java.io.Writer out)
用于执行JspFragment对象所代表的JSP代码片段
参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)
JspFragment.invoke方法可以说是JspFragment最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;
在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;
若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。
下面我们在来使用简单标签来实现上面的四个案例:
1、是否输出标签体内容
自定义标签类:
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 控制标签体是否执行
* @author weijiang204321
*
*/
public class SimpleTagDemo1 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
//相当于jf.invoke(null);
jf.invoke(this.getJspContext().getOut());
//这里如果不想输出标签体内容的话,只需要不调用invoke方法即可
}
}
我们看到这个和传统标签不一样,这里只有一个doTag方法了,在这个方法中我们通过是否调用jf.invoke方法来控制是否执行标签体内容,这里还有一个问题是如果我们想输出标签体内容,只需要调用invoke方法即可,同时这个方法传递的参数是一个Writer对象,所以如果我们想将标签体内容输出到浏览器中只需要传递out对象到这个方法即可,但是如果将这个方法的参数设置成null的话也是向浏览器中输出标签体内容的
注册标签体类:
<!-- 是否显示标签体 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo1</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo1</tag-class>
<body-content>scriptless</body-content>
</tag>
这里的注册和传统标签不一样的就是标签的值是scriptless而不是JSP了
在JSP页面使用:
<simpletag:demo1>
aaa
</simpletag:demo1>
2、控制Jsp余下内容是否输出
自定义标签类:
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 控制不执行余下的jsp内容
* @author weijiang204321
*
*/
public class SimpleTagDemo2 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
//直接抛出异常就不会执行余下的jsp内容
throw new SkipPageException();
}
}
我们只要在doTag方法中抛出一个SkipPageException异常就可以实现不执行余下的Jsp内容
注册标签类:
<!-- 控制是否显示jsp页面 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo2</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo2</tag-class>
<body-content>empty</body-content>
</tag>
在Jsp页面中使用:
<simpletag:demo2/>
在这个标签之后的jsp页面内容就不会输出了
3、标签体重复执行
自定义标签类:
package com.weijia.sampletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 迭代标签体
* @author weijiang204321
*
*/
public class SimpleTagDemo3 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for(int i=0;i<5;i++){
jf.invoke(null);
}
}
}
这里就比传统标签的操作简单了,直接写在for循环中,在循环中调用invoke方法即可
注册标签类:
<!-- 控制标签体重复输出 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo3</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo3</tag-class>
<body-content>scriptless</body-content>
</tag>
在Jsp页面中使用:
<simpletag:demo3>
aaaa
</simpletag:demo3>
4、修改标签体内容
package com.weijia.sampletag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 修改标签体
* @author weijiang204321
*
*/
public class SimpleTagDemo3 extends SimpleTagSupport{
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
StringWriter sw = new StringWriter();
jf.invoke(sw);
String content = sw.toString();
content = content.toUpperCase();
this.getJspContext().getOut().write(content);
}
}
我们将StringWriter对象传递到invoke方法中,然后再通过StringWriter对象得到标签体内容,进行操作,然后再通过out对象输出到浏览器中。
注册标签类:
<!-- 修改标签体内容 -->
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo4</name>
<tag-class>com.weijia.simpletag.SimpleTagDemo4</tag-class>
<body-content>scriptless</body-content>
</tag>
在Jsp页面中使用:
<!-- 修改标签体内容 -->
<simpletag:demo4>
bbbb
</simpletag:demo4>
这样我们就介绍了怎样使用传统标签和简单标签来编写自己的标签,这样我们就可以将任何java代码移到标签类中,然后在jsp页面中使用标签即可。那么最后再来看一下传统标签和简单标签的继承关系:
1. JspTag接口
JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0以前的版本中只有Tag接口,所以把实现Tag接口的自定义标签也叫做传统标签,把实现SimpleTag接口的自定义标签叫做简单标签。本书中如果没有特别说明,自定义标签泛指传统标签。
2. Tag接口
图6.5中的Tag接口是所有传统标签的父接口,其中定义了两个重要方法(doStartTag、doEndTag)方法和四个常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE、SKIP_PAGE),这两个方法和四个常量的作用如下:
(1)WEB容器在解释执行JSP页面的过程中,遇到自定义标签的开始标记就会去调用标签处理器的doStartTag方法,doStartTag方法执行完后可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就会接着执行自定义标签的标签体;如果doStartTag方法返回SKIP_BODY,WEB容器就会忽略自定义标签的标签体,直接解释执行自定义标签的结束标记。
(2)WEB容器解释执行到自定义标签的结束标记时,就会调用标签处理器的doEndTag方法,doEndTag方法执行完后可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE。如果doEndTag方法返回常量EVAL_PAGE,WEB容器就会接着执行JSP页面中位于结束标记后面的JSP代码;如果doEndTag方法返回SKIP_PAGE,WEB容器就会忽略JSP页面中位于结束标记后面的所有内容。
从doStartTag和doEndTag方法的作用和返回值的作用可以看出,开发自定义标签时可以在doStartTag方法和doEndTag方法体内编写合适的Java程序代码来实现具体的功能,通过控制doStartTag方法和doEndTag方法的返回值,还可以告诉WEB容器是否执行自定义标签中的标签体内容和JSP页面中位于自定义标签的结束标记后面的内容。
2. IterationTag接口
IterationTag接口继承了Tag接口,并在Tag接口的基础上增加了一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。实现IterationTag接口的标签除了可以完成Tag接口所能完成的功能外,还能够通知WEB容器是否重复执行标签体内容。对于实现了IterationTag接口的自定义标签,WEB容器在执行完自定义标签的标签体后,将调用标签处理器的doAfterBody方法,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就会把标签体内容再重复执行一次,执行完后接着再调用doAfterBody方法,如此往复,直到doAfterBody方法返回常量SKIP_BODY,WEB容器才会开始处理标签的结束标记和调用doEndTag方法。
可见,开发自定义标签时,可以通过控制doAfterBody方法的返回值来告诉WEB容器是否重复执行标签体内容,从而达到循环处理标签体内容的效果。例如,可以通过一个实现IterationTag接口的标签来迭代输出一个集合中的所有元素,在标签体部分指定元素的输出格式。
在JSP API中也提供了IterationTag接口的默认实现类TagSupport,读者在编写自定义标签的标签处理器类时,可以继承和扩展TagSupport类,这相比实现IterationTag接口将简化开发工作。
3. BodyTag接口
BodyTag接口继承了IterationTag接口,并在IterationTag接口的基础上增加了两个方法(setBodyContent、doInitBody)和一个EVAL_BODY_BUFFERED常量。实现BodyTag接口的标签除了可以完成IterationTag接口所能完成的功能,还可以对标签体内容进行修改。对于实现了BodyTag接口的自定义标签,标签处理器的doStartTag方法不仅可以返回前面讲解的常量EVAL_BODY_INCLUDE或SKIP_BODY,还可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED,WEB容器就会创建一个专用于捕获标签体运行结果的BodyContent对象,然后调用标签处理器的setBodyContent方法将BodyContent对象的引用传递给标签处理器,WEB容器接着将标签体的执行结果写入到BodyContent对象中。在标签处理器的后续事件方法中,可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。
在JSP API中也提供了BodyTag接口的实现类BodyTagSupport,读者在编写能够修改标签体内容的自定义标签的标签处理器类时,可以继承和扩展BodyTagSupport类,这相比实现BodyTag接口将简化开发工作。
4. SimpleTag接口
SimpleTag接口是JSP2.0中新增的一个标签接口。由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,因此,SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口。SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,并且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。关于SimpleTag接口的详细介绍本书将在第7章详细讲解。
在JSP API中也提供了SimpleTag接口的实现类SimpleTagSupport,读者在编写简单标签时,可以继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工作。
为方便读者日后查询传统标签接口中的各个方法可以返回的返回值,笔者在表6.1列举了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它们分别可以返回的返回值的说明。
下面我们来看一下如何开发一个具有属性的自定义标签的内容:
要想让一个自定义标签具有属性,通常需要完成两个任务:
在标签处理器中编写每个属性对应的setter方法
在TLD文件中描术标签的属性
为自定义标签定义属性时,每个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收JSP页面调用自定义标签时传递进来的属性值。 例如属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
在标签处理器中定义相应的set方法后,JSP引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法,为标签设置属性。
在TLD文件中的描述规格是为:
<tag>元素的<attribute>子元素用于描述自定义
标签的一个属性,自定义标签所具有的每个属性
都要对应一个<attribute>元素 。
<attribute>
<description>description</description>
<name>aaaa</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>ObjectType</type>
</attribute>
其中的各个属性值的含义如下:
那么下面就来看一个实例,通过一个属性值来控制标签体的内容输出的次数:
自定义标签类:
package com.weijia.propertytag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class PropertyTag extends SimpleTagSupport{
private int count = 0;
public void setCount(int count){
this.count = count;
}
@Override
public void doTag() throws JspException, IOException {
JspFragment jf = this.getJspBody();
for(int i=0;i<count;i++){
jf.invoke(null);
}
}
}
这里需要定义一个变量来记录执行的次数,同时还需要提供set方法
注册这个标签:
<tag>
<description>
Catches any Throwable that occurs in its body and optionally
exposes it.
</description>
<name>demo</name>
<tag-class>com.weijia.propertytag.PropertyTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>
Name of the exported scoped variable for the
exception thrown from a nested action. The type of the
scoped variable is the type of the exception thrown.
</description>
<name>count</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
我们这里设置这个属性的名称是count,而且这个属性在标签中是必须设置的,同时这个标签可以使用表达式
在Jsp页面中使用:
<propertytag:demo count="9">
aaaaa
</propertytag:demo>
在页面中输出9次aaaaa
虽然我们这里看到了输出的很简单,设置也很简单,但是这里面还是有很多内容的
首先来看一下,我们在Jsp页面中输入的是字符串,但是我们定义的count是int类型,没有报错,所以这里他做了类型转换,当然这个不是能够转换所有的类型的,只能转化8中基本类型,比如我们定义了一个属性是Date类型的,当我们在Jsp页面中传递”1990-08-01”这样就会报错的,当然我们可以使用脚本表达式进行属性的赋值是可以的,比如:
<propertytag:demo count="<%=new Date()%>">
aaaaa
</propertytag:demo>
在来看一下,他是怎么定位到属性count的,这个其实在学习Java基础知识的时候就说过,在学习JavaBean的相关知识的时候,我们知道一个Bean对象的属性的概念,比如这里我们定义了一个count变量,同时设置了他的set方法,那么这个count就是一个属性,但是属性的概念不是通过变量名来定义的,而是通过set方法来定义的,比如我们这里可以将count变量名改成counts,但是setCount方法名不变,我们运行程序,仍然不会报错的,但是我们将setCount方法名改成setCounts的时候,运行程序就报错了,原因也很好理解,他在进行变量count进行设置值的时候,会通过set方法来进行设置,这时候就会通过setXXX来找到相对应的set方法,从而能够对每个变量的值设置正确。这个相关内容其实我们在之前介绍标签的时候讲到过,这个技术在JavaWeb中很常用的,专门用来操作Bean对象的(内省技术BeanUtils)