jsp 标签文件
一. 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(2 * length); for( int i =0; 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",65); %> <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="220" border="1" cellspacing="0"> <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>
浏览器结果