自定义标签
一、自定义标签概述
使用标准JSP访问、操作JavaBean,是实现展现(HTML)与业务实现(Java代码)分离的第一步。然而,标准方法功能不够强大,以至于开发者无法仅仅使用它们开发应用,还要在JSP页面中使用Java代码。
介于JavaBean中解决展现与业务实现分离的方法的不完善,就产生了JSP1.1中的自定义标签。自定义标签提供了在JavaBean中所不能实现的便利。其中就包括,自定义标签允许访问JSP隐藏的对象及它们的属性。
尽管自定义标签能编写无脚本的JSP页面,但是JSP1.1和JSP1.2中提供的经典自定义标签非常难用。直到JSP2.0,才增加两个特性,用于改善自定义标签实现。第一个特性是一个接口——SimpleTag。另一个特性是标签文件中定义标签的机制。
自定义标签的实现,叫做标签处理器,而简单标签处理器是指继承SimpleTag实现的标签处理器。
二、简单标签处理器
实现SimpleTag的标签处理器都叫作简单标签处理器;实现Tag、Iteration及BodyTag的标签处理器都叫作经典标签处理器。
简单标签处理器有着简单的生命周期,而且比经典标签处理器更加容易实现。SimpleTag接口中用于标签触发的方法只有一个——doTag,并且此方法只执行一次。业务逻辑、遍历及页面内容操作都在这里实现。简单标签处理器中的页面内容都在JspFragment类的实例中体现。
简单标签的生命周期如下:
- JSP容器通过简单标签处理器的无参数构造器创建它的实例。
- JSP容器通过setJspContext的方法,传入JspContext对象。
- 如果自定义标签被另一个自定义标签所嵌套,JSP容器就会调用setParent的方法。
- JSP容器调用该标签中所定义的每个属性的set方法。
- 如果需要处理页面内容,JSP容器还会调用SimpleTag接口的setJspBody方法,把使用JSPFragment封装的页面内容传过来。
javax.servlet.jsp.tagext 包中也包含了一个SimpleTag的基础类:SimpleTagSupport。它提供了SimpleTag所有方法的默认实现。
三、实例
自定义标签需要有两个步骤:编写标签处理器及注册标签。
1. 编写标签处理器
package customtag; import java.io.IOException; import java.util.StringTokenizer; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; public class DataFormatterTag extends SimpleTagSupport { private String header; //标签属性 private String items; public void setHeader(String header) { this.header = header; } public void setItems(String items) { this.items = items; } public void doTag() throws IOException, JspException { //标签处理函数 JspContext jspContext = getJspContext(); //返回JspFragment关联的JspContext对象 JspWriter out = jspContext.getOut(); //通过JspContext实例中的getOut方法获取JspWriter对象 out.print("<table style='border:1px solid green'> " + "<tr><td><span style='font-weight:bold'>" + header +"</span></td></tr> "); StringTokenizer tokenizer = new StringTokenizer(items,","); while(tokenizer.hasMoreElements()) { String token = tokenizer.nextToken(); out.print("<tr><td>" + token + "</td></tr> "); } out.print("</table>"); } }
2. 注册标签
<?xml version="1.0" encoding="UTF-8"?> <taglib xlmns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun/xml/ns/j2ee web-jsptaglibrary_2_1/xsd" version="2.1"> <description>Simple tag examples</description> <!-- 标签库描述 --> <tlib-version>1.0</tlib-version> <jsp-version>1.1</jsp-version> <short-name>My First Taglib Example</short-name> <uri>/firstTag</uri> <!-- 为标签库设置URI,jsp通过它引入此标签库 --> <tag> <name>dataFormatter</name> <!-- 自定义标签名称 --> <tag-class>customtag.DataFormatterTag</tag-class> <!-- 标签处理器路径 --> <body-content>empty</body-content> <!-- 标签内容处理方式 --> <attribute> <name>header</name> <required>true</required> </attribute> <attribute> <name>items</name> <required>true</required> </attribute> </tag> </taglib>
3. 在JSP页面中使用自定义标签
1 <%@ taglib uri="/firstTag" prefix="easy" %> 2 3 <html> 4 <head> 5 <title>TestingFormatterTag</title> 6 </head> 7 8 <body> 9 <easy:dataFormatter header="States" items="Alabama,Alaska,Georgia,Florida"/> 10 11 <br/> 12 <easy:dataFormatter header="Countries"> 13 <jsp:attribute name="items"> 14 US,UK,Canada,Korea 15 </jsp:attribute> 16 </easy:dataFormatter> 17 18 </body> 19 </html>
运行效果:
四.处理属性
实现SimpleTag接口或者扩展SimpleTagSupport的标签处理器都可以有属性。下面的例子展示了名为DateFormatTag的标签处理器可以将逗号分隔内容转换成HTML表格。
①标签处理器
1 package customtag; 2 3 import java.io.IOException; 4 import java.util.StringTokenizer; 5 6 import javax.servlet.jsp.JspContext; 7 import javax.servlet.jsp.JspException; 8 import javax.servlet.jsp.JspWriter; 9 import javax.servlet.jsp.tagext.SimpleTagSupport; 10 11 public class DataFormatterTag extends SimpleTagSupport { 12 private String header; 13 private String items; 14 15 public void setHeader(String header) { 16 this.header = header; 17 } 18 19 public void setItems(String items) { 20 this.items = items; 21 } 22 23 public void doTag() throws IOException, JspException { 24 JspContext jspContext = getJspContext(); 25 JspWriter out = jspContext.getOut(); 26 27 out.print("<table style='border:1px solid green'> " + "<tr><td><span style='font-weight:bold'>" + header +"</span></td></tr> "); 28 StringTokenizer tokenizer = new StringTokenizer(items,","); 29 while(tokenizer.hasMoreElements()) { 30 String token = tokenizer.nextToken(); 31 out.print("<tr><td>" + token + "</td></tr> "); 32 } 33 34 out.print("</table>"); 35 } 36 }
②jsp页面实现
<%@ taglib uri="/WEB-INF/mytags.tld" prefix="easy" %> <html> <head> <title>TestingFormatterTag</title> </head> <body> <easy:dataFormatter header="States" items="Alabama,Alaska,Georgia,Florida"/> <br/> <easy:dataFormatter header="Countries"> <jsp:attribute name="items"> US,UK,Canada,Korea </jsp:attribute> </easy:dataFormatter> </body> </html>
③效果展示
五.访问标签内容
在SimpleTag中,可以通过JSP容器传入的JspFragment来访问标签内容。JspFragment类提供了多次访问Jsp中这部分代码的能力。JSP片段的定义不能包含脚本或者脚本表达式,他只能是文件模板或者JSP标准结点。
JspFragment类中有两个方法:getJspContext、invoke。定义如下:
public abstract JspContext getJspContext() public abstract void invoke(java.io.Writer writer) throws JspException, java.io.IOException
getJspContext方法返回这个JspFragment关联的JspContext对象。可以通过invoke方法来执行这个片段(标签的内容),然后通过指定的Writer对象把它直接输出。如果把null传入invoke方法中,那么这个Writer将会被JspFragment所关联的JspContext对象中的getOut方法返回的JspWriter所接管。下面看一个示例:
SelectElementTag
package customtag; import java.io.IOException; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; public class SelectElementTag extends SimpleTagSupport{ private String[] countries = {"Australia", "Brazil", "China"}; public void doTag() throws IOException, JspException { JspContext jspContext = getJspContext(); JspWriter out = jspContext.getOut(); out.print("<select> "); for(int i=0;i<3;i++){ getJspContext().setAttribute("value", countries[i]); getJspContext().setAttribute("text", countries[i]); getJspBody().invoke(null); } out.print("</select> "); } }
注册SelectElementTag
<tag> <name>select</name> <tag-class>customtag.SelectElementTag</tag-class> <body-content>scriptless</body-content> </tag>
selectElementTagTest页面
<%@ taglib uri="/WEB-INF/mytags.tld" prefix="easy" %> <html> <head> <title>Testing SelectElementFormatterTag</title> </head> <body> <easy:select> <option value="${value}">${text}</option> </easy:select> </body> </html>
效果
六、编写EL函数
一般来说,编写EL函数需要以下两个步骤:
(1)创建一个包含静态方法的public类。每个类的静态方法表示一个EL函数。这个类可以不需要实现任何接口或者继承特定的类。可以像发布任何类一样发布这个类。这个类必须放在应用中的/WEB-INF/classes目录或者它的子目录下。
(2)用function节点在标签库描述其中注册这个函数。
- function节点是taglib节点的下级节点,它有如下子节点:
- description:可选,标签说明。
- display-name:在XML工具中显示的缩写名字。
- icon:可选,在XML工具中使用的icon节点。
- name:函数的唯一名字。
- function-class:该函数对应实现的Java类的全名。
- function-signature:该函数对应实现的Java静态方法。
- example:可选,使用该函数的示例说明。
- function-extension:可以是一个或者多个节点,在XML工具中使用,用于提供该函数的更多的细节。
要使用这个函数,必须将taglib指令中的URI属性指向标签库描述,并指明使用的前缀。然后在JSP页面中使用如下语法来访问该函数:
${ prefix:functionName(parameterList) }
具体看以下示例:
StringFunction类中的reverseString方法
package function; public class StringFunctions { public static String reverseString(String s){ return new StringBuffer(s).reverse().toString(); } }
functiontags.tld文件
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://wwww.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_1.xsd" version="2.1"> <description> Function tag examples </description> <tlib-version>1.0</tlib-version> <function> <description>Reverses a String</description> <name>reverseString</name> <function-class>function.StringFunctions</function-class> <function-signature> java.lang.String reverseString(java.lang.String) </function-signature> </function> </taglib>
使用EL函数
<%@ taglib uri="/WEB-INF/functiontags.tld" prefix="f" %> <html> <head> <title>Testing reverseString function</title> </head> <body> ${f:reverseString("Hello World") } </body> </html>
效果
七、发布自定义标签
可以吧自定义的标签处理器以及标签描述器打包到JAR包里,这样就可以把它发布出来给别人使用了,就像JSTL一样。这种情况下,需要包含其所有的标签处理器及描述它们的TLD文件。此外,还需要在描述其中的URI节点中指定绝对的URI。
为了在应用中使用这个库,需要把这个JAR文件拷贝到应用的WEB-INF/lib目录下。在使用的时候,任何使用自定义标签的JSP页面都要使用和这个标签库描述器中定义的URL。
------ 天若有情天亦老,人间正道是沧桑 ------