最近弄了一个分页插件,顺便学习了下自定义标签、自定义函数、taglib的使用。
使用自定义标签首先需要了解自定义标签的层次结构,见下图:
通常我们自定义类继承TagSupport、BodyTagSupport,在对应方法中实现我们自己的逻辑。相关类:
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
目标一: 简单标签使用,使用自定义标签实现用户信息输出,效果如下:
开发步骤:
第一步:定义简单标签处理程序MyTag.java,继承TagSupport,重写doStartTag()方法,内容如下:
package com.tag; import java.io.IOException; import javax.servlet.http.Cookie; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport; public class MyTag extends TagSupport{ private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private int age; @Override public int doEndTag() throws JspException { this.releaseResource(); return super.doEndTag(); } @Override public int doStartTag() throws JspException { JspWriter out = this.pageContext.getOut(); try { out.print("<table><tr><td>username</td><td>"+username+"</td><td>age</td><td>"+age+"</td></tr></table>"); out.print("<table><tr><td>request username</td><td>"+this.pageContext.getRequest().getAttribute("username")+"</td><td>reqeust age</td><td>"+this.pageContext.getRequest().getAttribute("age")+"</td></tr></table>"); } catch (IOException e) { e.printStackTrace(); } return this.EVAL_BODY_INCLUDE; } public void releaseResource(){ this.username=null; this.age=0; } @Override public void release() { this.releaseResource(); super.release(); } }
注意username,age参数的set传入方式,在TagSupport里面最为重要的方法就是doStartTag,doEndTag,release,doAfterBody,另外自定义标签处理程序可以与JSP容器互通有无,主要是通过PageContext来传递和输送数据。PageContext作为一个桥梁在jsp容器这自定义处理程序中非常重要。
第二步:编写自定义标签处理程序描述文件,在WEB-INF/目录下,建立myself.tld文件,文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <taglib version="2.0" 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"> <tlib-version>1.0</tlib-version> <jsp-version>2.0</jsp-version> <shortname>user</shortname> <uri>http://www.jb-aptech.com.cn/taglibs/common</uri> <tag> <name>user</name> <tagclass>com.tag.MyTag</tagclass> <bodycontent>jsp</bodycontent> <attribute> <name>username</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>age</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
描述文件表面自定义处理程序需要username,age两个参数。
第三步:配置web.xml
<jsp-config> <taglib> <taglib-uri>/mytaglib</taglib-uri> <taglib-location>/WEB-INF/myself.tld</taglib-location> </taglib> </jsp-config>
第四步:在jsp页面中使用,新建tagTest.jsp,内容如下:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" import="com.tag.*,java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib uri="/mytaglib" prefix="cc" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <% request.setAttribute("username","wangwu"); request.setAttribute("age","141"); User user = new User(); user.setUsername("lisi"); user.setAge(238); request.setAttribute("user",user); %> <cc:user age="23" username="wujie"></cc:user> <br/> <cc:user age="${requestScope.age}" username="${requestScope.username}"></cc:user> <br /> </body> </html>
注意导入语句:
<%@ taglib uri="/mytaglib" prefix="cc" %>
应该和web.xml中配置的的taglib-uri一样,也可以不在web.xml中配置,直接指向自定义描述符号文件,如下:
<%@ taglib uri="/WEB-INF/myself.tld" prefix="cc" %>
接下来就是使用:
<cc:user age="23" username="wujie"></cc:user>
age和username作为参数通过set方法注入,传递到自定义标签处理程序供程序使用。
一个简单的自定义表情处理程序已经完成。
Tag的处理流程如下,Tag是最为简单的标签处理流程:
我们创建的MyTag类继承了TagSupport类,而它又实现了Tag接口,Tag接口的生命周期由其所在的容器控制,如上图:
setPageContext() 将所在jsp页面的pageContext注入进来,目的是为了在后面的方法中可以访问到jsp页面对象的pageContext属性
setParent() 设置此标签的父标签
setAttribute() 将标签中的属性注入到此class的属性,不需要自己实现但要提供属性的get与set方法
doStartTag() 在开始标签属性设置后调用,如果返回SKIP_BODY则忽略标签之中的内容,如果返回EVAL_BODY_INCLUDE则将标签体的内容进行输出
doEndTag() 在结束标签之前调用,返回SKIP_PAGE跳过整个jsp页面后面的输出,返回EVAL_PAGE执行页面余下部分
release() 生命周期结束时调用
特别说明:在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\目录
开发建议:最好确保自定义标签是无状态的(也就是除传递的set参数外,没有其他实例变量),确保多次访问之间不会相互影响,如果确实有实例变量,那么记得每次在调用doEndTag方法的时候释放或者清空资源。
-------------------------------------------------------------------------------------------------------------------------------
TagSupport类已经为我们实现并扩展了一些方法(比如在上述方法中我们可以直接使用pageContext对象,调用父标签getParent()等),所以一般情况下我们只需重写doStartTag(),doEndTag() 即可
TLD文件说明:
<!--版本号-->
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>cc</short-name>
<tag>
<!—指定标签名 -->
<name>showUserInfo</name>
<!—指定标签类文件的全路径 -->
<tag-class>com.mytags.UserInfoTag</tag-class>
<!--如果不需要标签体则设置empty,反之设定jsp -->
<body-content>empty</body-content>
<!—设定属性(如果有的话) -->
<attribute>
<!—指定标签名 -->
<name>user</name>
<!—是否是必须,如果非必须没设置则为空 -->
<required>false</required>
<rtexprvalue>true</rtexprvalue><!—是否可在属性中使用表达式 -->
</attribute>
</tag>
Web.xml文件说明:
<jsp-config>
<taglib>
<!--
标签库的uri路径
即jsp头文件中声明<%@ taglib uri="/mytaglib" prefix="cc"%>
的uri
-->
<taglib-uri>/mytaglib </taglib-uri>
<!—tld文件所在的位置-->
<taglib-location>/WEB-INF/mytaglib.tld</taglib-location>
</taglib>
</jsp-config>
目标二:实现对迭代标签使用,IteratorTag
效果:
开发步骤:
假设我们已经有如下User的javaBean:
package com.tag; public class User { private String username; private int age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
自定义标签处理程序,MyIteratorTag.java:
要完成此功能我们需要实现一个迭代接口,即IterationTag,由于TagSupport同样实现了此接口,所以我们继承此类
package com.tag; import java.util.Iterator; import java.util.List; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; public class MyIteratorTag extends TagSupport { private List<User> list = null; private String var = ""; private Iterator it = null; public String getVar() { return var; } public void setVar(String var) { this.var = var; } public List<User> getList() { return list; } public void setList(List<User> list) { this.list = list; } int i = 0; @Override public int doAfterBody() throws JspException { while (it.hasNext()) { i++; pageContext.setAttribute(var, it.next()); return this.EVAL_BODY_AGAIN; } System.out.println(i); return this.SKIP_BODY; } @Override public int doStartTag() throws JspException { if (list == null || list.size() == 0) { return this.SKIP_BODY; } it = list.iterator(); if (it.hasNext()) { this.pageContext.setAttribute(var, it.next()); } return this.EVAL_BODY_INCLUDE; } @Override public int doEndTag() throws JspException { this.releaseResource();//切记务必记得释放资源,防止前一次操作对后一次访问造成影响 return super.doEndTag(); } public void releaseResource() { this.i=0; this.list=null; this.var = ""; } @Override public void release() { this.releaseResource(); super.release(); } }
添加标签描述符:
<tag> <name>myIteratorTag</name> <tagclass>com.tag.MyIteratorTag</tagclass> <bodycontent>jsp</bodycontent> <attribute> <name>list</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
配置web.xml,上面已经配置,不在讲述
在jsp页面中使用:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" import="com.tag.*,java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib uri="/WEB-INF/myself.tld" prefix="cc" %> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <% List<User> userList = new ArrayList<User>(); User user1 = new User(); user1.setUsername("user1"); user1.setAge(11); User user2 = new User(); user2.setUsername("user2"); user2.setAge(22); User user3 = new User(); user3.setUsername("user3"); user3.setAge(33); User user4 = new User(); user4.setUsername("user4"); user4.setAge(44); userList.add(user1); userList.add(user2); userList.add(user3); userList.add(user4); pageContext.setAttribute("user",userList); %> <table> <tr> <td>username</td> <td>age</td> </tr> <cc:myIteratorTag var="user" list="${pageScope.user }"> <tr> <td>${user.username }</td> <td>${user.age }</td> </tr> </cc:myIteratorTag> </table> </body> </html>
IteratorTag相比Tag接口,多出了一个doAfterBody方法,用于在处理完标签体之后,做判断处理使用,依据返回值是否需要再次处理,doAfterBody返回EVAL_BODY_AGAIN,表明再次对标签体进行处理。
IteratorTag的处理流程如下:
此目标并不会使用实际例子进行显示,主要是说明为什么,什么情况下需要使用到BodyTag接口或者BodyTagSupport类?
如果我们需要在<test> …. </test>之间的标签体的头部和尾部加上一些标记或者是其他处理,一般的处理方法是在doStartTag和doEndTag方法中进 行, 但是如果是个迭代标签,标签体的每段内容在循环输出时每次都需要在头部和尾部加上一些标记,我们使用BodyTagSupport就很方便了,
此接口在doStartTag()方法返回值多了一个EVAL_BODY_BUFFERED,它将对主体进行计算,并输出到缓冲区(注:此处是缓冲 区并非直接输出到客户端,需要我们手动 (this.bodyContent.getEnclosingWriter().write(this.bodyContent.getString());) 进行输出客户端的调用,否则主体内容不会进行显示)
标签类说明:
关于BodyTagSupport接口的说明
简单使用例子:
还是和上面的例子一样,只不过我们不继承TagSupport,我们继承BodyTagSupport,代码如下:
package com.tag; import java.io.IOException; import java.util.Iterator; import java.util.List; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; import javax.servlet.jsp.tagext.TagSupport; public class MyBodyTagSupport extends BodyTagSupport { private List<User> list = null; private String var = ""; private Iterator it = null; public String getVar() { return var; } public void setVar(String var) { this.var = var; } public List<User> getList() { return list; } public void setList(List<User> list) { this.list = list; } int i = 0; @Override public int doAfterBody() throws JspException { while (it.hasNext()) { i++; pageContext.setAttribute(var, it.next()); return this.EVAL_BODY_BUFFERED; } System.out.println(i); return this.SKIP_BODY; } @Override public int doStartTag() throws JspException { if (list == null && list.size() == 0) { return this.SKIP_BODY; } it = list.iterator(); if (it.hasNext()) { this.pageContext.setAttribute(var, it.next()); } return this.EVAL_BODY_BUFFERED; } @Override public int doEndTag() throws JspException { String content = this.bodyContent.getString().replaceAll("user", "TestUser"); try { this.bodyContent.getEnclosingWriter().write(content); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.releaseResource();//切记务必记得释放资源,防止前一次操作对后一次访问造成影响 return super.doEndTag(); } public void releaseResource() { this.i=0; this.list=null; this.var = ""; } }
注意doAfterBody返回值为EVAL_BODY_BUFFERED,表明标签体的内容处理完后并没有直接输出到客户端,而是放到BodyContent对象里,供后面判断使用,在doEndTag方法中,通过
String content = this.bodyContent.getString().replaceAll("user", "TestUser");
调整修改标签体计算的内容作为最后的输出内容,虽然这段代码没有任何含义,只是演示而已,但表明BodyTagSupport在处理标签体内容时,可以在标签体处理完毕之后修改其内容,来适应业务需求。输出到客户端需要调用如下代码:
this.bodyContent.getEnclosingWriter().write(content);
配置自定义描述符文件:
<tag> <name>myBodyTagSupprot</name> <tagclass>com.tag.MyBodyTagSupport</tagclass> <bodycontent>jsp</bodycontent> <attribute> <name>list</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>var</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
配置web.xml,同上
在jsp页面中使用,如下:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" import="com.tag.*,java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib uri="/WEB-INF/myself.tld" prefix="cc" %> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <% List<User> userList = new ArrayList<User>(); User user1 = new User(); user1.setUsername("user1"); user1.setAge(11); User user2 = new User(); user2.setUsername("user2"); user2.setAge(22); User user3 = new User(); user3.setUsername("user3"); user3.setAge(33); User user4 = new User(); user4.setUsername("user4"); user4.setAge(44); userList.add(user1); userList.add(user2); userList.add(user3); userList.add(user4); pageContext.setAttribute("user",userList); %> <table> <tr> <td>username</td> <td>age</td> </tr> <cc:myBodyTagSupprot var="user" list="${pageScope.user }"> <tr> <td>${user.username }</td> <td>${user.age }</td> </tr> </cc:myBodyTagSupprot> </table> </body> </html>
可以很好的与spring、DAO等框架整合,
上述自定义标签 编写起来不是非常方便,需要注意的地方比较多,在JSP2.0中提供了一种更为便捷的方式实现上面的功能,叫做taglib, 现在就以简单标签实现的功能来举例实现taglib的使用,taglib可以理解为jsp页面的内容片断,仅仅是将jsp页面公用的部分给抽取出来:
第一步:在WEB-INF/tags/目录下新建myself.tag,输入如下内容:
<%@tag pageEncoding="UTF-8"%> <%@ attribute name="username" required="true" rtexprvalue="true"%> <%@ attribute name="age" required="true" rtexprvalue="true"%> <table> <tr> <td>tag username:</td><td>${username }</td> <td>tag age:</td><td>${age }</td> </tr> </table>
注意:参数引入是通过attribute方式引入的,其require、rtexprvalue和自定义标签描述符的含义是一样的。
第二步:在jsp页面中之间引用:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" import="com.tag.*,java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <% request.setAttribute("username","wangwu"); request.setAttribute("age","141"); User user = new User(); user.setUsername("lisi"); user.setAge(238); request.setAttribute("user",user); %> <tags:myself age="100" username="zhangsan"></tags:myself> <br/> =================================================<br/> <tags:myself age="${user.age }" username="${user.username }"></tags:myself> <br/> </body> </html>
两步就可以搞定上面需要4步搞定的内容,而且更加简洁、方便,因为taglib就是jsp页面的部分,所以jsp页面的隐式对象、pageContext都可以直接使用。
注意taglib的引入方式和自定义标签的引入方式区别:
自定义标签使用的是uri,指向在web.xml中配置的taglib-uri或者直接指向文件目录 <%@ taglib uri="/WEB-INF/myself.tld" prefix="cc" %> taglib方式使用的是tagdir,指向的是tag文件的存放目录,引用的时候直接用文件名作为区分,如<tags:myself <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
为了方便在jsp页面通过el表达式一样调用普通java对象的方法。在jsp2.0提供了自定义函数,使用如下:
普通javabean,注意调用的函数必须是static静态方法,新建MyFunction.java,内容如下:
package com.tag; public class MyFunction { public static int add(int i ,int j){ return i+j; } }
在自定义标签描述符文件文件中添加如下方法描述:
<function> <name>add</name> <function-class>com.tag.MyFunction</function-class> <function-signature>java.lang.Integer add(int, int) </function-signature> </function>
使用:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8" import="com.tag.*,java.util.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib uri="/WEB-INF/myself.tld" prefix="cc" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
3+4=${cc:add(3,4) }
</body>
</html>