JSP自定义标签
JSP标签是一种XML元素,在功能上与JavaBean类似,都是通过封装Java代码,组成可重用的组件,并为复杂的操作提供逻辑名称,使JSP网页变得简洁并易于维护。JavaBean是通过提供接口供外部操作调用实现逻辑功能,而自定义标签则是通过标签形式为外部操作实现逻辑功能。
由于标签是XML元素,所以它的名称和属性都是大小写敏感的。
在JSP页面中,我们主要通过标签库来使用标签。标签库是按照功能或实现进行分组的自定义标签的集合。一些jar包/class和tld文件共同组成了一个标签库,jsp文件中通过下面的代码引入标签库:
<%@ taglib prefix="fn" uri="/WEB-INF/tld/fn.tld"%>
url属性是必须的,它是用来设定被包含页面的地址,可以是绝对地址也可以是相对地址。如果使用相对地址,可把同一个Web应用中的文件引入,如果使用绝对路径,可将其他网站的文件包含进来。
JSP标签虽然已不是一项新的技术,但在Web开发中,我们还是会常常用到一些JSP的标签库,最常见的就是JSTL库。有时,我们还需要开发我们自己的标签库。如何来开发自已的标签库呢?通常有以下几个步骤:
[1] 创建标记处理器。在标记处理器中,通常一是提供属性的set方法,这样这个属性就可以在jsp页面设置;二是处理 doStartTag 或 doEndTag。在 doStartTag 里通常进行初始化、流程选择操作,而在 doEndTag 里进行后续页面输出控制。如果在服务器端处理标签中的正文或则是嵌套标签时的话,还需处理doAfterBody方法。
[2] 编写tld文件。把所编写的标签信息以xml形式通知容器,这样容器就可以解释tag组件了。
[3] 在jsp页面导入tld。这样,jsp页面就可以使用自定义的标签了。
首先我们来创建创建标签的处理类(Tag Handler Class)。标签处理类需要实现Tag接口。
这里以一个时间格式化处理为例来说明:
import java.io.IOException; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.tagext.TagSupport; //无正文标签类是继承自TagSupport类实现Tag接口。 public class DateTagNoBody extends TagSupport { @Override public int doStartTag() throws JspException { HttpServletRequest request; // TagSupport类中定义的一个属性,它是javax.servlet.jsp.PageContext的对象 request = (HttpServletRequest) pageContext.getRequest(); java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("yyyy-MM-dd"); String date = formater.format(new Date()); JspWriter out = pageContext.getOut(); try { out.print(date); } catch (IOException e) { e.printStackTrace(); } // doStartTag() 方法返回 SKIP_BODY。原因是我们的简单日期标记没有正文。 return Tag.SKIP_BODY; } } |
一个tag其实就是一个继承了TagSupport或BodyTagSupport的普通java类。TagSupport/BodyTagSupport这两个类是由jsp容器提供的,它们有一些方法负责处理jsp页面和编写的类之间的交互,例如输入,输出,无须开发人员自己来实现。换句话说,我们只需要继承TagSupport或者BodyTagSupport来定义实现了业务逻辑的类,就变成了一个Tag类,并且这个Tag类自己负责与jsp 页面的交互。
第二步,创建标签库描述文件(Tag Library Descrptor File)
TLD文件中的元素可以分成3类: A.标签库元素、B.标签元素、C.标签属性元素
标签库元素用来设定标签库的相关信息,它的常用属性有:
A.shortname:指定Tag Library默认的前缀名(prefix);
B.uri:设定Tag Library的惟一访问表示符。
<?xml version="1.0" encoding="UTF-8"?> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <short-name>cc</short-name> <uri>/mytaglib</uri> <tag> <name>displayDate</name> <tagclass>sample.DateTagNoBody</tagclass> <bodycontent>empty</bodycontent> </tag> </taglib> |
第三步,在JSP文件中动态引用标签库
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="/WEB-INF/datetag.tld" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <c:displayDate/> </body> </html> |
如果是进行静态引用,那么则应先在web.xml文件中配置元素,然后在JSP文件中引人标签库
<jsp-config> <taglib> <taglib-uri>/mytaglib</taglib-uri> <taglib-location>/WEB-INF/datetaglib.tld</taglib-location> </taglib> </jsp-config> |
然后,将JSP 声明加入到所有需要使用自定义标记库的页面中:
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="mytaglib" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <c:displayDate/> </body> </html> |
指定的uri 属性与在web.xml 文件中指定的taglib-uri 值相匹配。
在进行标记库的静态引用时,JSP 声明必须查询 web.xml 文件以执行库查询。这意味着如果移动或者重命名了库,或者希望在 web.xml 文件中加入更多的库,就必须停止服务器、更新 web.xml 文件、然后重新启动服务器。而动态方法让JSP页直接指向 TLD 位置,因而是在解释JSP页面时进行处理。
以上定义的标签是无正文无属性的标签,它仅仅只是一个标记,实用性不强。如果在标签中包含了自定义的属性,那么在标签处理类中就应当把这个属性作为成员变量,并且分别提供设置和读取属性的方法。下面再定义一个无正文但带属性的标签定义,带属性无正文的标签库创建过程与上面的过程是相同的。
第一步,定义JSP自定义标签处理类
import java.io.IOException; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.tagext.TagSupport; //如果是有正文的标签类则继承的是BodyTagSupport类,实现BodyTag接口 public class DateTagNoBody extends TagSupport { private String pattern; @Override public int doStartTag() throws JspException { HttpServletRequest request; request = (HttpServletRequest) pageContext.getRequest(); java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat(pattern); String date = formater.format(new Date()); JspWriter out = pageContext.getOut(); try { out.print(date); } catch (IOException e) { e.printStackTrace(); } return Tag.SKIP_BODY; }
//必须实现setXX()方法 public void setPattern(String pattern){ this.pattern = pattern; } } |
带属性无正文的标签与无属性无正文的标签处理上唯一的不同在于实现了一个对属性设置的set方法。
第二步,是定义tld文件
<?xml version="1.0" encoding="UTF-8"?> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <tag> <name>displayDate</name> <tagclass>sample.DateTagNoBody</tagclass> <bodycontent>empty</bodycontent> <!-- 定义属性 --> <attribute> <name>pattern</name> <!-- 属性名字 --> <type>String</type> <!-- 属性类型 --> <requried>false</requried> <!-- 是否必须 --> <rtexprvale>false</rtexprvale> <!-- 表示是否可以使用JSP表达式 --> </attribute> </tag> </taglib> |
第三步,在JSP文件中动态引用
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="/WEB-INF/datetag.tld" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <c:displayDate pattern='yyyy-MM-dd'/> </br> <c:displayDate pattern='MM/dd HH:mm:ss'/> </body> </html> |
显然,更常用的形式是带正文且带属性的标签,它的创建过程如下:
第一步,定义JSP自定义标签处理类
import java.io.IOException; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTagSupport;
public class BodyTag extends BodyTagSupport { private int count; private HttpServletRequest reqeust; private JspWriter out;
public void init() { reqeust = (HttpServletRequest) pageContext.getRequest(); out = pageContext.getOut(); }
@Override public int doStartTag() throws JspException { init(); return this.EVAL_BODY_INCLUDE; }
//设置当前标签体 @Override public void setBodyContent(BodyContent bodyContent) { this.bodyContent = bodyContent; System.out.println("setBodyContent..."); }
//需要初始化bodyContent @Override public void doInitBody() throws JspException { System.out.println("init....."); }
@Override public int doAfterBody() throws JspException { if (count >= 1) { try { out.println(count); out.println("<Br>"); } catch (IOException e) { e.printStackTrace(); } count --; return this.EVAL_BODY_AGAIN; } else { return this.SKIP_BODY; } }
@Override public int doEndTag() throws JspException { java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat( "yyyy-MM-dd"); String date = formater.format(new Date()); try { out.print(date); } catch (IOException e) { e.printStackTrace(); } return this.EVAL_PAGE; }
// 必须实现setXX()方法 public void setCount(int count) { this.count = count; } } |
在标签处理类中增加了对标签正文的处理。它的执行顺序如下:
doStartTag()->setBodyContent()->doInitBody()->doAfterTag()->doEndTag()
如果doStartTag()返回的是EVAL_BODY_INCLUDE执行doAfterTag()方法,如果它返回SKIP_BODY就执行doEndTag()方法。
setBodyContent()方法用于设置标签体内容,如果在计算BodyContent时需要进行一些初始化工作,则在doInitBody()方法中完成。标签体内容执行完后,会调用doAfterBody()方法。
在doAfterTag()方法中返回EVAL_BODY_AGAIN来重复执行doAfterTag()方法。
返回SKIP_BODY值则执行doEndTag()方法,在doEndTag()方法中返回EVAL_PAGE值,则执行此标签的后的其它代码,返回SKIP_PAGE则不执行此页面的其它代码。
第二步,定义tld文件
<?xml version="1.0" encoding="UTF-8"?> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <tag> <!-- 标签元素用来定义一个标签 --> <name>iterator</name> <!-- Tag的名字--> <tagclass>sample.BodyTag</tagclass> <!-- Tag的处理类--> <bodycontent>jsp</bodycontent> <!-- 标签的主体(body)内容,其取值包括: 1)empty:表示标签中没有body; 2)JSP:表示标签的body中可以加入JSP程序代码; 3)tagdependent:表示标签中的内容由标签自己去处理。 --> <attribute> <!-- 定义标签属性 --> <name>count</name> <!-- 属性名字,名字可任意取,只要类里提供相应的set方法即可。 --> <type>int</type> <!-- 属性类型 --> <requried>false</requried> <!-- 是否必须 --> <rtexprvale>false</rtexprvale> <!-- 属性值是否可以为request-time表达式,即表示是否可以使用JSP表达式,也就是类似于< %=…% >的表达式 --> </attribute> </tag> </taglib> |
在JSP页面中动态引用
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="/WEB-INF/bodytag.tld" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <c:iterator count="10">HelloWorld!</c:iterator> <% out.println("Bye Bye"); %> </body> </html> |
我们再来看一下TagSupport类
1、TagSupport类的主要属性:
A.parent属性:代表嵌套了当前标签的上层标签的处理类;
B.pageContex属性:代表Web应用中的javax.servlet.jsp.PageContext对象;
2、TagSupport类提供了两个处理标签的方法:
public int doStartTag() throws JspException
public int doEndTag() throws JspException
JSP容器在调用doStartTag或者doEndTag方法前,会先调用setPageContext和setParent方法,设置pageContext和parent。因此在标签处理类中可以直接访问pageContext变量;但在TagSupport的构造方法中不能访问pageContext成员变量,因为此时JSP容器还没有调用setPageContext方法对pageContext进行初始化。
3、doStartTag:当JSP容器遇到自定义标签的起始标志,就会调用doStartTag()方法(在开始标签属性设置后调用)。 doStartTag()方法返回一个整数值,用来决定程序的后续流程。
A.Tag.SKIP_BODY:表示?>…之间的内容被忽略;
B.Tag.EVAL_BODY_INCLUDE:表示标签之间的内容被正常执行(将标签体的内容进行输出)。
4、doEndTag:但JSP容器遇到自定义标签的结束标志,就会调用doEndTag()方法(在开始标签属性设置后调用)。doEndTag()方法也返回一个整数值,用来决定程序后续流程。
A.Tag.SKIP_PAGE:表示立刻停止执行网页,网页上未处理的静态内容和JSP程序均被忽略任何已有的输出内容立刻返回到客户的浏览器上。
B.Tag_EVAL_PAGE:表示按照正常的流程继续执行JSP网页(将标签体的内容进行输出)。
5、其它方法:
setPageContext() 将所在jsp页面的pageContext注入进来,目的是为了在后面的方法中可以访问到jsp页面对象的pageContext属性
setParent() 设置此标签的父标签
setAttribute() 将标签中的属性注入到此class的属性,不需要自己实现但要提供属性的get与set方法
release() 生命周期结束时调用
Tag系列的Interface里定义的静态int,通过他们也能一窥tag组键的执行流程,这几个静态值分别是:
SKIP_BODY : 跳过了开始和结束标签之间的代码,一般是在doStartTag中使用
EVAL_BODY_INCLUDE :处理嵌套的标签,一般是在doStartTag中使用,由负责处理标签正文的tag接口提供
EVAL_BODY_BUFFERED :对包含的内容进行解析 一般是在doStartTag中使用,由负责处理标签正文的bodyTag接口提供,目的是通知jsp容器作好读取正文的工作(创建一个body-content包装正文和获取存放操作结果的out对象,便于以后的操作和输出).
EVAL_BODY_AGAIN:处理标签正文,嵌套标签的iteratorTag接口的使用
SKIP_PAGE : 忽略剩下的页面,一般是在doEndTag中使用
EVAL_PAGE : 继续执行下面的页, 一般是在doEndTag中使用
特别说明:在tomcat4.1之后的版本中默认开启了标签缓冲池(websphere和weblogic并不会这么做),所以执行完标签后并不会执行release()方法(_jspDestroy()时才释放),也就是说同一个jsp页面自定义标签不管使用多少次只会存在一个实例,但也并不是每一个标签都会为其创建一个缓冲池,要根据参数来判断,例如: <cc:UserInfoTag user=”…” /> <cc:UserInfoTag /> 上面例子中由于参数不同就会创建两个标签缓冲池。 这个问题可以通过设定tomcat的配置文件加以解决: 在%tomcat%\conf\web.xml加入enablePooling参数,并设置为false(不缓存自定义标签)。 <init-param> <param-name>enablePooling</param-name> <param-value>false</param-value> </init-param> 清空%tomcat%\conf\目录 |
使用BodyTagSupport说明
如果我们需要在<test> …. </test>之间的标签体的头部和尾部加上一些标记或者是其他处理,一般的处理方法是在doStartTag和doEndTag方法中进行, 但是如果是个迭代标签,标签体的每段内容在循环输出时每次都需要在头部和尾部加上一些标记,我们使用BodyTagSupport就很方便了,
此接口在doStartTag()方法返回值多了一个EVAL_BODY_BUFFERED,它将对主体进行计算,并输出到缓冲区(注:此处是缓冲区并非直接输出到客户端,需要我们手动(this.bodyContent.getEnclosingWriter().write(this.bodyContent.getString());)进行输出客户端的调用,否则主体内容不会进行显示)
参考:http://developer.51cto.com/art/200907/134263.htm
http://www.cnblogs.com/zhaoyang/archive/2011/12/25/2301108.html