一. tag file 简介
tag file从两个方面简化了自定义标签的开发。首 先,tag file无须提前编译,直到第一次被调用才会编 译。除此之外,仅仅使用JSP语法就可以完成标签的扩 展定义,这意味着不懂Java的人也能够进行标签自定义 了。
其次,标签库描述文件也不再需要了。原先需要在 标签库描述文件里定义标签元素的名字,以及它所对应 的action。使用tag file的方式,tag file名和action相同, 因此不再需要标签库描述文件了。
JSP容器提供多种方式将tag file编译成Java的标签 处理类。例如Tomcat将tagfile翻译成继承于 javax.servlet.jsp.tagext.SimpleTag接口的标签处理类。
一个tag file和JSP页面一样,它拥有指令、脚本、 EL表达式、动作元素以及自定义的标签。一个tag file 以tag和tagx为后缀,它们可以包含其他资源文件。一个 被其他文件包含的tag file应该以tagf为后缀。
tag文件必须放在应用路径的WEB-INF/tags目录下 才能生效。和标签处理类一样,tag 文件可以被打到jar 包里。
tag file中也有一些隐藏对象,通过脚本或者EL表 达式可以访问这些隐藏对象。表7.1列出了这些隐藏对 象。这些隐藏对象和JSP隐藏对象类 似。
对象 | 类型 |
request | javax.servlet.http.HttpServletRequest |
response | javax.servlet.http.HttpServletResponse |
out | javax.servlet.jsp.JspWriter |
session | javax.servlet.http.HttpSession |
application | javax.servlet.ServletContext |
config | javax.servlet.ServletConfig |
jspContext | javax.servlet.jsp.JspContext |
二.第一个 tag file
1. 下面的这个例子包含一个tag文件和一个使用这个tag文 件的JSP页面。例子的目录结构如图所示。
这个tag file的名称是firstTag.tag,代码如下所 示。
<%@ tag import="java.util.Date" import="java.text.DateFormat"%>
<%
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
Date now = new Date(System.currentTimeMillis());
out.println(dateFormat.format(now));
%>
tag file和JSP页面是很相似 的。在firstTag.tag文件里包含了一个tag指令和一个脚本 片段,其中tag指令里又有两个import属性。接下来,只 需要将这个tag file放到WEB-INF/tags目录下就可以使用 了。注意tag file名和标签的名字是一样的,例如这个 firstTag.tag的tag file对应的标签名即为firstTag。
2. 下面是一个使用firstTag.tag文件的JSP实例: firstTagTest.jsp。
firstTagTest.jsp页面
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags" %>
Today is <easy:firstTag/>
效 果:
三. tag file 指令
和JSP页面一样,tag file可以使用指令来指挥JSP容 器如何编译这个tag file。tag file的指令语法和JSP是一 样的:
<%@ directive (attribute="value")* %>
星号(*)表示括号内的可以重复0次或者多次,上 面的指令也可以写成下面这种更直白的样子:
<%@ directive attribute1="value1" attribute2="value2" ... %>
属性必须被单引号或者双引号包裹,<%@之后和 %>之前的空格加不加都不影响正确性,但是为了可读 性,建议加上空格。除了page指令,其他所有的JSP指 令都可以用于tag file。在tag file中,可以使用tag指令代 替page指令。另外,你还可以使用两个新指令: attribute 和variable。下表展示了所有可以在tag file中使 用的指令。
指令 | 描述 |
tag | 作用与JSP页面中的page指令类似 |
include | 用于将其他资源导入tag file中 |
taglib | 用于将自定义标签库导入tag file中 |
attribute | 用于将自定义标签库导入tag file中 |
variable | 用于将自定义标签库导入tag file中 |
1. tag指令
tag指令和JSP页面中的page指令类似。以下是它的 使用语法:
<%@ tag (attribute="value")* %>
也可以使用下面这种更直白的表达式:
<%@ tag attribute1="value1" attribute2="value2" ... %>
属性 | 描述 |
display-name | 在XML工具中显示的名称。默认值是不包含后缀的tagfile名 |
body-content | 指定标签body的类型,body-content属性值有empty、tagdependent、scriptless,默认值是scriptless |
dynamicattributes | 指定tagfile动态属性的名称。当dynamicattributes值被设定时,会产生一个Map来存放这些动态属性的名称和对应的值 |
small-icon | 指定一个图片路径,用于在XML工具上显示小图标。一般不会用到 |
large-icon | 指定一个图片路径,用于在XML工具上显示大图标。一般也不会用到 |
description | 标签的描述信息 |
example | 标签使用实例的描述 |
language | tagfile中使用的脚本语言类型。当前版本的JSP中,该值必须设为“java” |
import | 用于导入一个java类型,和JSP页面中的import相同 |
pageEncoding | 指定tagfile使用的编码格式,可以使用“CHARSET”中的值。和JSP页面中的pageEncoding相同 |
除了import属性,其他所有的属性在一个tag指令或 一个tag file中都只能出现一次。例如,以下的tag file就 是无效的,因为body-content属性在同一个tag file中出 现了多次:
<%@ tag display-name="first tag file" body-content="scriptless"
%>
<%@ tag body-content="empty" %>
2. include 指令
ag file中的include指令和JSP页面中的include指令 是一样的。可以使用这个指令来将外部文件导入到tag file中。当你有一个公共资源文件有可能用在多个tag file中时,include指令将能够发挥它的作用。这个公共 资源文件可以是静态文件(例如HTML文件),也可以 是动态文件(例如其他tag file)。
注意: 被导入的tag file片段必须以tagf为后缀。
例如: includeDemoTag.tag文件就导入了 一个静态文件(included.html)和一个动态文件 (included.tagf)。
includeDemoTag.tag文件
<%@ tag language="java" pageEncoding="UTF-8"%>
This tag file shows the use of the include directive.
The first include directive demonstrates how you can include
a static resource called included.html.
<br/>
Here is the content of included.html:
<%@ include file="included.html" %>
<br/>
<br/>
The second include directive includes another dynamic resource:
included.tagf.
<br/>
<%@ include file="include.tagf" %>
included.html 和included.tagf文件它们都和tag file放在同一个目录下。注意: 被导入的tag file片段必须以tagf为后缀。
included.html 文件
<table>
<tr>
<td><b>Menu</b></td>
</tr>
<tr>
<td>CDs</td>
</tr>
<tr>
<td>DVDs</td>
</tr>
<tr>
<td>Others</td>
</tr>
</table>
included.tagf文件
<%
out.println("hello from included.tagf");
%>
includeDemoTagTest.jsp 页面
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags" %>
<easy:includeDemoTag/>
3.taglib指令
可以通过taglib指令在tag file中使用自定义标签。 taglib指令的语法如下:
<%@ taglib uri="tagLibraryURI" prefix="tagPrefix" %>
其中uri属性用来指定与前缀相关联的标签库描述 文件的绝对路径或相对路径。
prefix属性用来定义自定义标签的前缀。
使用taglib指令,你可以像下面那样使用不包含 content body的自定义标签:
<prefix:tagName/>
当然,也可以使用包含content body的自定义标 签:
<prefix:tagName>body</prefix:tagName>
taglibDemo.tag导入了firstTag.tag来显 示服务器日期,taglibDemoTest.jsp调用了 taglibDemo.tag。
taglibDemo.tag页面
<%@ taglib prefix="simple" tagdir="/WEB-INF/tags" %>
The server's date: <simple:firstTag />
jsp页面
<easy:IncludeDemoTag />
4. attribute 指令
attribute用于设定tag file中标签的属性。它和标签 库描述文件中的attribute元素等效。下面是该指令的语 法:
<%@ attribute (attribute="value")* %>
也可以用以下更直白的方式表达:
<%@ attribute attribute1="value1" attribute2="value2" ... %>
attribute指令的属性参见下表。其中只有name属性 是必须的。
属性 | 描述 |
name | 用于设定该属性的名称。在一个tag file中,每个属性的名称必须是唯一的 |
required | 用于设定该属性是否是必须的。值可以取true或false,默认值为flase |
fragment | 用于设定该属性是否是fragment。默认值为false |
rtexprvalue | 用于设定该属性的值是否在运行时被动态计算。值可以取true或false,默认值为true |
type | 用于设定该属性的类型,默认值为java.lang.String |
description | 用于设定该属性的描述信息 |
下面是一个例子,encode.tag文件可用 于对一个字符串进行HTML编码。这个encode标签定义 了一个input属性,该属性的类型是java.lang.String。
encode.tag文件
<%@ tag language="java" pageEncoding="UTF-8" %>
<%@ attribute name="input" required="true" %>
<%!
private String encodedHtmlTag(String tag){
if ( tag == null) return null;
int length = tag.length();
StringBuilder encodedTag = new StringBuilder( * length);
for( int i =; i < length; i++)
{
char c = tag.charAt(i);
if( c == '<')
encodedTag.append("<");
else if ( c == '>')
encodedTag.append(">");
else if ( c == '&')
encodedTag.append("&");
else if ( c == '"')
encodedTag.append("&qout");
else if ( c == ' ')
encodedTag.append(" ");
else
encodedTag.append(c);
}
return encodedTag.toString();
}
%>
<%=encodedHtmlTag(input) %>
encodeTagTest.jsp文件
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags" %>
<easy:encode input="<br/> means changing line"/>
5. variable 指令
有时候我们需要将tag file中的一些值传递到JSP页 面。这时候通过variable来完成。tag file中的variable指 令和标签库描述文件中的variable元素类似,它用于定 义那些需要传递到JSP页面的变量。
tag file支持多个variable指令,这意味着可以传递 多个值到JSP页面。相对而言,attribute 指令的作用与 variable相反,它用于将值从JSP页面传递到tag file。
variable指令的语法如下:
<%@ variable (attribute="value")* %>
也可以用下面这样更直白的表达方式:
<%@ variable attribute1="value1" attribute2="value2" ... %>
属性 | 描述 |
name-given | 变量名。在JSP页面的脚本和EL表达式中,可以使用该变量名。如果指定了name-from-attribute属性,那么name-given属性就不能出现了,反之亦然。name-given的值不能和同一个tag file中的属性名重复 |
name-from-attribute | 和name-given属性类似,由标签属性的值来决定变量的名称。如果namefrom-attribute和name-gien属性同时出现或者都不出现的话会出现错误 |
alias | 设定一个用来接收变量值的局部范围 |
variableclass | 变量的类型。默认为java.lang.String |
declare | 设定该变量是否声明。默认值为false |
scope | 用于指定该变量的范围。可取的值为AT_BEGIN、AT_END、和NESTED。默认值为NESTED |
description | 用于描述该变量 |
你或许会奇怪,既然JSP页面可以调用JspWriter来 接收变量值了,为什么还需要通过variable指令来传递 变量值呢。那是因为通过JspWriter只能简单地将一个 String传递到JSP页面,灵活性很差。举一个例子,清单 7.1中的firstTag.tag用于输出服务器当前日期的长格式。 但是如果你还需要输出服务器日期的短格式的话,就必 须再写一个tag file。写两个功能类似的tag file显然是冗 余工作。如果使用变量指令,就没有这样的问题了,只 需要在tag file中定义longDate和shortDate两个变量就可 以了。
例如,varDemo.tag提供了输出服务 器当前日期长格式和短格式的两个功能,它定义了两个 变量:longDate和shortDate。
<%@ tag language="java" pageEncoding="UTF-8" %>
<%@ tag import="java.util.Date" import="java.text.DateFormat" %>
<%@ variable name-given="longDate" %>
<%@ variable name-given="shortDate" %>
<%
Date now = new Date();
DateFormat longFormat = DateFormat.getDateInstance(DateFormat.LONG);
DateFormat shortFormat = DateFormat.getDateInstance(DateFormat.SHORT);
jspContext.setAttribute("longDate", longFormat.format(now));
jspContext.setAttribute("shortDate",shortFormat.format(now));
%>
<jsp:doBody />
注意,这里使用了jspContext.setAttribute方法来设 置变量。jspContext是一个隐藏对象。JSTL中的set标签 也能实现同样的功能,如果对JSTL熟悉,可以使用set 标签来代替setAttribute方法。JSTL在第5章“JSTL”中介 绍过。 同时需要注意的是,这里使用了doBody动作元素 来调用这个标签体。
varDemoTest.jsp页面
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
Today's date:
<br/>
<tags:varDemo>
In long format: ${longDate}
<br/>
In short format: ${shortDate}
</tags:varDemo>
在很多情况下都需要用到变量。比如说,你希望实 现一个这样的功能:根据产品标识从数据库中获取该产 品的详细信息。你可以通过一个属性来传递产品标识。 然后可以用多个变量来保存产品的详细信息,每个变量 对应为产品的每个属性。最终,你会用到例如name、 price、description、imageUrl等变量。
四. doBody
doBody动作元素只能在tag file中使用,它用来调用 一个标签的本体内容。
doBody动作元素也可以有属性。你可以通过这些 属性来指定某个变量来接收主体内容,如果不使用这些 指令,那么doBody动作元素会把主体内容写到JSP页面 的JspWriter上。
属性 | 描述 |
var | 用于保存标签主体内容的变量值,主体内容就会以java.lang.String的类型保存这个变量内。var和varReader属性只能出现一个 |
varReader | 用于保存标签主体内容的变量值,主体内容就会以java.io.Reader的类型保存这个变量内。var和varReader属性只能出现一个 |
scope | 变量保存到的作用域 |
下面的这个例子说明了如何用doBody来调用标签 本体内容并将内容保存在一个叫作referer的变量中。假 设你有一个卖玩具的网站,并且在多个搜索引擎上做了 这个玩具网站的广告。现在你想要知道每个搜索引擎为 玩具网站带来的流量有多少转化成了购买行为。为了做 到这点,你可以记录每个网站首页访问的referer头部信 息,使用一个tag file来将referer头信息保存到session属 性中。如果某个用户在后续购买了产品,就可以从 session属性中获得referer头信息,并记录在数据库中。
这个例子包含了一个HTML文件 (searchEngine.html)、两个JSP文件(main.jsp 和 view Referer.jsp)以及一个tag file (doBodyDemo.tag)。 main.jsp页面是玩具网站的首页,使用了doBodyDemo 标签来保存referer头信息。viewReferer.jsp页面用来查 看收集到的referer头信息。如果直接通过URL访问 main.jsp,那么referer头信息即为null。因此你必须通过 searchEngine.html来链接到main.jsp页面。
doBodyDemo.tag页面
<jsp:doBody var="referer" scope="session"/>
没错,doBodyDemo.tag只有一行:一个doBody动 作元素。它指定了一个叫作referer的session属性来保存标签本体内容。
main.jsp页面
Your referer header: ${header.referer}
<br/>
<tags:doBodyDemo>
${header.referer} <!-- 这个值会传给doBodyDemo.tag 里的 referer1 变量-->
</tags:doBodyDemo>
<a href="viewReferer.jsp">View</a> the referer as a Session attribute.
main.jsp页面通过文本和EL表达式输出referer头信息,紧接着,输出一个指向ViewReferer页面的链接
viewReferer.jsp页面
The referer header of the previous page is ${sessionScope.referer1}<!-- 打印refere1的值 -->
浏览器结果
五. invoke
invoke动作元素和doBody类似,在tag file中,可以 使用它来调用一个fragment。还记得在定义属性的 attribute指令中有一个fragment属性,它的值可以是true 或者false。如果fragment值为true,那么这个属性就是 一个fragment,这意味着可以从tag file中多次调用。 invoke动作元素也有多个属性,表7.7展示了invoke动作 元素中的全部属性,其中fragment属性是必须的。
属性 | 描述 |
fragment | 要调用的fragment的名称 |
var | 用于保存片段主体内容的变量值,主体内容就会以java.lang.String的类型保存这个变量内。var和varReader属性只能出现一个 |
varReader | 用于保存标签主体内容的变量值,主体内容就会以java.io.Reader的类型保存这个变量内。var和varReader属性只能出现一个 |
scope | 变量保存到的作用域 |
invokeDemo.tag是一个invoke动作元 素的例子。
invokeDemo.tag 文件
<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ attribute name="productDetails" fragment="true" %>
<!-- 因为invoke 是用来调用fragment 的,如果fragment="false" 的话 连续按F5刷新页面会报错 -->
<%@ variable name-given="productName" %>
<%@ variable name-given="description" %>
<%@ variable name-given="price" %>
<%
jspContext.setAttribute("productName", "Pelesonic DVD Player");
jspContext.setAttribute("description","Dolby Digital output through coaxial digital-audio jack,"
+" 500 lines horizontal resolution-image digest viewing");
jspContext.setAttribute("price",);
%>
<jsp:invoke fragment="productDetails" />
invokeDemo.tag中使用了attribute指令,并且将 fragment属性值设为true。另外还定义了三个变量。最 后调用了productDetails的fragment。由于在invoke动作 元素中,var和varReader都没有设置,因此fragment的内 容将直接传递到JSP页面的JspWriter中。
invokeTest.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Product Details</title>
</head>
<body>
<easy:invokeDemo>
<jsp:attribute name="productDetails">
<table width="" border="" cellspacing="">
<tr>
<td><b>Product Name</b></td>
<td>${productName}</td>
</tr>
<tr>
<td><b>Description</b></td>
<td>${description}</td>
</tr>
<tr>
<td><b>Price</b></td>
<td>${price}</td>
</tr>
</table>
</jsp:attribute>
</easy:invokeDemo>
</body>
</html>
浏览器结果