doStartTag、doAfterBody、doEndTag返回值总结... 55
include指令与标签引用的文件中自定义标签中SKIP_PAGE影响... 66
EL表达式语言
EL概述
基本语法格式: ${表达式}
特点:
l 在EL表达式中可以直接使用属性名来引用存储在各种域范围(page, request,session, application)中的属性,例如,$(user)等效于如下的JSP脚本表达式:<%= pageContext.findAttribute("user") %>
l 在EL表达式中可以使用${customerBean.address.country}的形式来访问JavaBean对象中的属性对象中的属性,以及可以使用${users[0]}的形式来访问有序集合中的元素。在EL表达式中可以执行基本的关系运算、逻辑运算和算术运算,例如,下面的EL表达式输出的结果为false:${ 1 > (4/2)}
l 在EL表达式中可以使用自定义函数来完成一些更复杂的功能,例如,表达式${it315:filter("<img/>")}中的it355:filter是用户自定义的函数,"<img/>"则是传递给这个自定义函数的参数。
l 在EL表达式中可以使用一系列的隐含对象,例如,pageContext、cookie等。通过这些隐含对象,EL表达式可以访问JSP页面中的各种信息,例如,通过cookie隐含对象可以直接访问到某个Cookie信息,如果不使用EL表达式,则必须编写复杂的JSP脚本代码才能访问某个Cookie信息。
在JSP文件中,字符串“${”作为EL表达式的开始标记,所以,如果要输出字面意义的字符串“${”,必须便用反斜杠对“$”字符进行转义,例如,要输出字符串“an expression is ${true}”,在JSP文件中必须写成字符串“an expression is \${true}”。如果要在EL表达式内部包含“$”字符或“${”字符串,只需将它们作为一个普通的字符串常量用引号引起来即可,例如,“${"${"}”的输出结果就是“${”字符串。另外,在支持EL表达式的JSP页面中,单个的“$”也可以使用“\$”来表示,但这不是必需的。
EL基本应用
在JSP 2.0中,EL表达式可以用于JSP模板和任何能接受动态值的JSP标签的属性中(包括JSTL标签的属性、自定义标签的属性、JSP标准标签的属性等)。
JSP标签的属性值中使用EL表达式
<c:out value= "Dear ${user.userName} from ${user.address},welcome you to our website!"/>
它先计算value属性中的EL表达式并将结果转换为字符串,然后和其他的静态字符串连接成一个新的字符串,最后再输出。
JSP模板中使用EL表达式
one value is ${bean1.a} and another is ${bean2.a.c}
JSP引擎首先计算各个EL表达式的值,然后将计算结果和静态文本一起输出到JSP页面。
注:在很多情况下,我们需要对那些特殊的HTML字符进行转换后输出,所以,我们通常不要将EL表达式直接用于JSP模板中,而是应该使用<c:out>这个JSTL标签来输出EL表达式的结果,因为<c:out>标签默认会对HTML和XML中的特殊字符进行转换后再输出。
忽略JSP页面中的EL表达式
在JSP 2.0以前没有EL表达式语言,所以,如果JSP 2.0以前的JSP文件中包含了“${”之类的字符序列,JSP引擎将把它们当作普通的字符文本来处理。但是,当把以前编写的JSP文件发布到支持JSP 2.0的Web服务器中运行时,JSP引擎就会将该JSP文件中的“${”字符序列当作EL表达式进行解析和执行,这样就会出现新版本的Web服务器不能兼容原来的JSP文件的问题。所以,JSP 2.0为了向后兼容以前的JSP页面,提供了在JSP页面中忽略EL表达式的设置选项。
在单个JSP页面里:
<%@ page isELIgnored="true" %>
如果是使用所有JSP页面都忽略,则在web.xml中设置:
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
忽略JSP页面中的脚本元素
在JSP页面中使用EL表达式可以实现JSP脚本元索的一些功能,并使JSP页面变得非常简洁,特别是对于严格遵循MVC模式的项目,在JSP页面中完全可以只使用EL表达式,而不必使用任何JSP脚本元素。JSP 2.0规范也为此提供了相应的支持,使得我们可以在Web应用程序中进行设置,让应用程序中的JSP页面中只能使用EL表达式,而不能有JSP脚本元素。JSP默认是支持JSP脚本元素的,只要在Web应用程序的web.xml配置文件中进行一些特殊的设置,就可以禁止所有JSP页面中出现脚本元素。如果JSP页面中使用了脚本元素,就会抛出异常。
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
隐含对象
对于EL表达式中出现的标识符,EL引擎首先判断这个标识符是否是一个隐含对象,如果是,则返回相应的隐含对象,否则把这个标识符当作一个域属性来看待,调用pageContext.findAttribute返回这个域属性所对应的对象,如果不存在标识符所对应的域属性,则返回结果为null。
如果在某个域中定义的属性的名称正好等于某个EL隐含对象的名称,在EL表达式中引用这个名称时,EL引擎将把它当作隐含对象看待,而不是返回域属性的值。所以隐含对象优先。
不要将EL表达式语言中的隐含对象与JSp隐含对象混淆,其中只有pageContext对象是它们所共有的,其他隐含对象则毫无相关性和可比性。
pageContext隐含对象
EL表达式中的pageContext对应于JSP页面中的pageContext对象,在EL表达式可以通过pageContext隐含对象访问其他JSP隐含对象,这也是将pageContext对象也作为EL表达式中的一个隐含对象的主要原因。可以通过pageContext隐含对象来访问request, response, servletContext和servletConfig等JSP内置对象。
<%@ page contentType="text/html;charset=gb2312" isELIgnored="false"%>
请求URI为:${pageContext.request.requestURI}<hr />
Content-Type响应头为:${pageContext.response.contentType}<hr />
服务器信息为:${pageContext.servletContext.serverInfo}<hr />
Servlet注册名为:${pageContext.servletConfig.servletName}
代表特定域属性集合的隐含对象
pageScope、requestScope、sessionScope和applicationScope这四个隐含对象分别用于访问JSP页面的page, request,session和application四个域中的属性。例如,表达式${pageScope.userName}返回page作用域中的userName属性的值,表达式${sessionScope.bookName}返回session作用域中的bookName属性的值。
在EL表达式中也可以不使用这些隐含对象来指定查找域,而是直接引用这些域中的属性名称。例如,表达式${userName}在page, request, session和application这四个作用域内按顺序查找userName属性,直到找到为止。
代表请求参数集合的隐含对象
隐含对象param和paramValues用于获取客户端访问JSP页面时传递的请求参数的值。因为HTTP协议允许一个请求参数名出现多次,即一个请求参数可能会有多个值,所以,EL表达式提供了param和paramValues这两个隐含对象来分别获取请求参数的单个值和所有值。
param隐含对象返回一个参数的单个值,如果同个请求参数有多个值,则返回第一个值。paramValues隐含对象第一个参数值。paramValues隐含对象用于返回一个请求参数的所有值,返回结果为该参数的所有值组成的字符串数组。例如,假设访问JSP页面时传递了两个名称都为"productID"的参数,参数值分别为“123”和"456",那么,表达式${param.productID}的返回值为字符串“123”,表达式${paramValues.productID)的返回值是一个字符申数组,数组的第一个元素为“123”,可以用表达式${paramValues.productID[0]}来获得,第二个对象为“456”,可以用表达式${paramValues.productID[1]}来获得。
代表HTTP请求消息头集合的隐含对象
隐含对象header和headerValues用于获取客户端访包JSP页面时传递的请求头字段的值。因为HTTP协议允许一些请求头字段可以出现多次,所以,EL表达式提供了header和headerValues这两个隐含对象来分别获取请求头字段的单个值和所有值。
header隐含对象返回一个请求头字段的单个值,如果同一个请求头字段有多个值,则返回第一个值,例如,使用表达式${header.referer}可以非常方便地获得referer请求头字段的值。headerValues隐含对象用于返回一个请求头字段的所有值,返回结果为该请求头字段的所有值组成的字符串数组。
cookie隐含对象
隐含对象cookie是一个代表所有Cookie信息的Map对象,其中Map对象中的各个元素的关键字分别为各个Cookie的名称,值则分别为Cookie名称对应的Cookie对象。使用cookie隐含对象可以访问某个Cookie名称对应的Cookie对象,这些Cookie对象是通过调HttpServletRequest.getCookies()方法得到的,如果多个Cookie信息共用一个名称,则返回Cookie名在Cookie对象数组中对应的第一个Cookie对象。
<%@ page contentType="text/html;charset=gb2312" %>
<%
response.addCookie(new Cookie("userName", "zxx"));
%>
打印名称为userName的Cookie对象的信息:${cookie.userName}<br>
打印名称为userName的Cookie对象的名称和值:${cookie.userName.name} = ${cookie.userName.value}
initParam隐含对象
servler.xml:
<Context path= "/myapp" docBase="E:\eclipse_workspace\servlet_jsp\myapp" crossContext="true" debug="0" reloadable="true">
<Parameter name="companyName1" value="XX" override="false"/>
<Parameter name="companyName2" value="XX" override="true"/>
</Context>
web.xml:
<context-param>
<param-name>companyName1</param-name>
<param-value>xxxx</param-value>
</context-param>
${initParam.companyName1} 的返回值为字符串"xxxx"。如果在server.xmlweb.xml文件中都指定了webSite参数,且server.xml文件中的<Parameterr>元素没有定override属性或override属性的值为true,则initParam隐含对象返回web.xml文件中指的参数值。
EL的null处理
l 如果域中没有名称为user的属性,即user变量映射的对象为null,则表达式${user}输出空字符串,而不是输出字符串“null”,显然在浏览中输出空字符串要比输出字符串“null”更友好,这正是使用EL表达式的一个重要好处。
l 如果域中没有名称为user的属性,虽然user变量映射的对象为null,但表达式${user.name}也是输出空字符串,而不是抛出空指针异常。
l 对于EL表达式${cookie.userName},即使cookie.userName引用的Cookie对象为null,JSP引擎也不会抛出空指针异常,而是输出空字符串。
l 如果EL表达式中的变量确实被映射到了一个JavaBean对象上,而这个JavaBean对象中不存在EL表达式中的变量所引用的属性,那么,JSP引擎就会抛出异常。
EL中的运算符
方括号运算符([ ])和点运行符(.)
(1) 这两个运算符都可以访问各个域属性对象中的属性和隐含对象的属性,在这种情况况下,它们的效果相同,可以互换使用。例如,表达式${user.name}和${user["name"])是等效的,都是用于访问存储在某个域中的user属性对象的name属性。
(2) [ ]运算符还可以访问有序集合(即实现了java.util.List接口的集合)或数组中的指定索引位里的某个元素,例如表达式${users[0]}用于访问集合或数组users中的第一个元素。在这种情况下,EL表达式中只能使用[ ]运算符,而不能使用点运算符。
(3) [ ]运算符还可以访问Map集合(即实现了java.util.Map接口的集合)指定关键字的某个元素:
<%@ page import="java.util.*"%>
<%
HashMap map = new HashMap();
String key = "xxx";
map.put(key, new Date());
pageContext.setAttribute("map", map);
%>
${map["xxx"]}
注:EL表达式中不能嵌套JSP脚本,如 ${<%= xx %>}是错误的,另外虽然${"<%= xx %>"}不报错,但Web容器不会去执行 <%= xx %> 脚本,而是直接看作是字符串,所以最终EL表达式不会有任何结果。
另外,发现使用“.”的访问方式也可以访问Map集合中的元素,如上面程序中EL表达式等效于 ${map.xxx}
(4) [ ]运算符和点运算符可以结合使用。例如,表达式${users[0].userName}可以访问集合或数组中的第一个元素对象的userName属性。
[ ]运算符中还可以使用表达式,如果表达式${a[b]}中的b不是一个字符串常量或数字常量时, 那么EL引擎将把这个b又当作一个EL变量或表达式来看待,表达式${a[b]}的值按如下规则进行计算:
(1) 如果表达式${a}的值为null,则表达式${a[b]}返回空字符串;
(2) 如果表达式${b}的值为null,则表达式${a[b]}返回空字符串;
(3) 如果${a}的值是一个java.util.Map对象,则${b}的值作为Map对象的关键字表达式${a[b]}返回${a}对象中关键字${b}的值;如果${a}对象中不存在关键字${b),则表表达式${a[b]}返回空字符串。
<%@ page import="java.util.*"%>
<%
HashMap map = new HashMap();
String key = "xxx";
map.put(key, new Date());
pageContext.setAttribute("map", map);
pageContext.setAttribute("b", key);
%>
${map[b]}
注:不能这样嵌套${map[${b}]},虽然看似与${map[b]}等效。
(4) 如果${a}的值是一个List或array对象,则根据EL表达式的转换规则将${b}的值转换为整数,表达式${a[b]}返回List或array对象指定索引位置的值。如果${b}的值不能转换为整数,则表达式${a[b]}发生错误;如果转换成功,但${b}的值小于0或者大于或等于对应的List或array对象的最大脚标,则表达式${a[b]}返回空字符串;如果List或array对象为空,则${a[b]}表达式产生异常。
<%@ page import="java.util.*"%>
<%
ArrayList list = new ArrayList();
list.add(new Date());
pageContext.setAttribute("list", list);
pageContext.setAttribute("index", "0");
%>
${list[index]}
(5) 如果${a}的值是一个JavaBean对象,则将${b}的值转换为String类型作为对象${a}的属性,如果JavaBean对象有${b}这个属性,则表达式${a[b]}就返回这个属性的值,否则表达式${a[b]}抛异常。
<%@ page import="mypak.*"%>
<%
TestBean tb = new TestBean();
tb.setName("jzj");
pageContext.setAttribute("bean", tb);
pageContext.setAttribute("attr", "name");
%>
${bean[attr]}
提示:如果在request域中存储了一个名称为“error.info”的属性,在EL表达式中如何访问这个属性的值呢?这显然不能使用${error.info}或{requestScope.error.info},而是只能使用${requestScope["error.info"]}。
算术运行符
+、-、*、/(div)、%(mod)
${10 / 2} 5.0
${10 div 2} 5.0
${10 % 4} 2
${10 mod 4} 2
比较运行符
==(或eq)、!=(或ne)、<(或lt)、>(或gt)、<=(或le)和>=(或ge)
<%
pageContext.setAttribute("a", "hello");
pageContext.setAttribute("b", "hi");
pageContext.setAttribute("c", new Float(1.0));
pageContext.setAttribute("d", new Integer(1));
%>
${a eq b}
${a lt b}
${c eq d}
逻辑运行符
&&(and)、||(or)、!(not)
<%
pageContext.setAttribute("a", "false");
pageContext.setAttribute("b", "true");
%>
${a and b}
${a or b}
empty运行符
检查某个变量是否为“null”或“空”。
表达式${empty A}按如下规则计算其返回值:
·当A指向的对象为加null时,表达式${empty A}返回true;
·当A是空字符串时,表达式${empty A}返回true;
·当A是集合(List、Map)或数组时,如果A中没有任何元素,表达式${empty A}返回true;
EL自定义函数
EL自定义函数就是在EL表达式中调用的某个Java类的静态方法,这个Java类的静态方法需要在Web应用程序中专门进行配置,才可以被EL表达式调用。
${namespace:trans(content)}
一般来说,EL自定义函数开发与应用包括以下三个步骤:
1) 编写EL自定义函数映射的Java类中的静态方法
package util;
public final class HTMLFilter {
public static String filter(String message) {
if (message == null) {
return (null);
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
2) 编写标签库描述符(tld)文件,在tld文件中描述自定义函数
为了能名让一个Java类的静态方法可以被EL表达式调用,需要在一个标签库描述文件(tld)中对EL自定义函数进行描述,以将静态方式映射成一个EL自定义函数。标签库描述符文件是一个XML格式的文件,它的扩展名为 .tld。标签库描述文件主要是用于自定标签,但也可以用来描述EL自定义函数。
<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib 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
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>function</short-name>
<uri>http://www.it315.org/function</uri>
<function>
<name>trans</name>
<function-class>util.HTMLFilter</function-class>
<function-signature>java.lang.String filter(java.lang.String)
</function-signature>
</function>
</taglib>
<uri>元素用指定该TLD文件的URI,在JSP文件中需要通过这个URI来引入该标签库描述文件。
<function>元素用于描述一个EL自定义函数,其中,<name>子元素用于指定EL自定义函数的名称,<function-class>子元素用于指定完整的Java类名,<function-signature>子元素用于指定Java类中的静态方法的签名,方法签名必须指明方法的返回值类型及各个参数的类型,各个参数之间用逗号分隔。一个标签库描述文件中可以有多个<function>元素,每个<function>元素分别用于描述一个EL自定义函数,同一个tld文件中的每个<function>元素中的<name>子元素设置的EL函数名称不能相同。
编写完标签库描述文件后,需要将它放里到WEB-INF目录中或WEB-INF目录下的除了classes和lib目录之外的任意子目录中。
3) 在JSP页面中导入和使用自定义函数
JSP2.0中定义了两种格式的JSP页面:标准的JSP页面和JSP文档,与此相对应,在JSP页面中引入标签库描述文件的方式也有两种:
·在标准的JSP页面中使用taglib指令;
·在jsp文档中使用XML名称空间。
1) 在标准的JSP页面中使用taglib指令
<%@ page contentType="text/html;charset=gb2312" %>
<%@ taglib prefix="it315" uri="http://www.it315.org/function" %>
<%
String content="<meta http-equiv='refresh' content='0;url=http://www.it315.org' />";
pageContext.setAttribute("content", content);
%>
${it315:trans(content)}
其中,uri属性用于指定所引入的标签库描述符文件中的URl,通常是标签库描述符文件中定义的<uri>元素的内容;prefix属性用于为引入的TLD文件指定一个“引用代号”,在JSP文件中调用TLD文件中的自定义标签或EL自定义函数时,需要将这个“引用代号”作为自定义标签或EL自定义函数的前缀,prefix属性的值可以任意指定,只要与其他taglib指令的prefix属性值不同就可以。
2) 在jsp文档中使用XML名称空间
我们可以使用<jsp:root>作为JSP文档的根元素,所以,可以在根元素<jsp:root>中引入标签库描述符文件。我们也可以将标签库描述符文件声明为默认的名称空间,这样在EL表达式中调用函数时就不用指定名称空间前缀了。
<?xml version="1.0" encoding="utf-8" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns="http://www.it315.org/function"
version="2.0">
<jsp:scriptlet>
<![CDATA[
String content=
"<meta http-equiv='refresh' content='0;url=http://www.it315.org' />";
pageContext.setAttribute("content", content);
]]>
</jsp:scriptlet>
<jsp:text>
${trans(content)}
</jsp:text>
</jsp:root>
自定义标签
可重用性
标签的几种形式
空标签
<tag:example/>
<tag:example></tag:example>
带体的标签
<tag:example>xxxxxxxxxx</tag:example>
<tag:example>
<%
out.print(xxx);
%>
</tag:example>
<tag:example>
<%=value%>
</tag:example>
<tag:example>
${value}
</tag:example>
嵌套标签
<tag:example>
<tag:example>
body
</tag:example>
</tag:example>
带属性标签
<tag:example attr1="" >
body
</tag:example>
简单开发与应用
第一步:编写用作标签处理器的Java类
需实现 javax.servlet.jsp.tagext.Tag 接口,为了简单,可直接继承自TagSupport类。
package org.it315;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class ViewIPTag extends TagSupport {
public int doStartTag() throws JspException {
String IP = pageContext.getRequest().getRemoteAddr();
try {
pageContext.getOut().write(IP);
} catch (IOException e) {
e.printStackTrace();
}
return super.doStartTag();
}
}
第二步:编写标签库描述文件
<taglib 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
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0"> <!--TLD文件的文件头,这部分信息通常是固定不变的-->
<tlib-version>1.0</tlib-version> <!--指定标签库的版本号-->
<short-name>SimpleTag</short-name> <!--指定标签库的名称-->
<uri>/taglib</uri> <!--指定该标签库的URI-->
<tag> <!--注册一个自定义标签-->
<name>viewIP</name> <!--指定自定义标签的注册名称-->
<!--指定标签的标签处理器类-->
<tag-class>org.it315.ViewIPTag</tag-class>
<!--指定标签体的类型,empty表示无标签体-->
<body-content>empty</body-content>
</tag>
</taglib>
<body-content>元素有以下四个取值:
.empty
表示在JSP页面中使用自定义标签时不能设置其标签体,否则JSP引擎会报错。
.JSP
表示自定义标签的标签体可以是任意JSP页面元素,注意JSP必须是大写。
.scriptless
表示自定义标签的标签体可以包含除JSP脚本元素之外的任意JSP页面元素。
.tagdependent
指JSP引擎对标签体内容不进行任何语义的解析,而是直接把标签体内容原封不动地输出给客户端或交给标签处理器自己去处理。例如,如果希望JSP引擎不要将标 签体中的“${...}”当作EL表达式处理,不要将标签体中的“<...>”当作标签处理, 不要将标签体中的“<%...%>”当作JSP脚本处理,而是将它们当作普通字符文本,那么,就应该设置<body-content>元素内容的值为tagdependent。
第三步:部署和安装自定义标签库
将.tld 文件放里到WEB-INF目录中或WEB-INF目录下的除了classes和lib目录之外的任意子目录中。
第四步:在JSP页面中导入和使用自定义标签
<%@ page contentType="text/html;charset=gb2312" %>
<%@ taglib uri="/taglib" prefix="it315" %>
<H1>您的IP地址是:<it315:viewIP/></H1>
自定义标签API
JspTag接口
JspTag接口是所有自定义标签的父接口,它是JSP 2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP 2.0以前的版本中只有Tag接口,所以把实现Tag接口的自定义标签也叫做传统标签,把实现SimpleTag接口的自定义标签叫做简单标签。本书中如果没有特别说明,自定义标签泛指传统标签。
Tag接口
Tag接口是所有传统标签的父接口,其中定义了两个重要方法(doStartTag、doEndTag)方法和四个常量 (EVAL_BODY_INCLUDE、EVAL_PAGE、SKIP_BODY、SKIP_PAGE),这两个方法和四个常量的作用如下:
1. Web容器在解释执行JSP页面的过程中,遇到自定义标签的开始标记就会去调用标签处理器的doStartTag方法,doStartTag方法执行完后可以向Web容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY,如返回EVAL_BODY_INCLUDE , Web容器就会接着执行自定义标签的标签体;如果返回SKIP_BODY,Web容器就会忽略自定义标签的标签体,直接解释执行自定义标签的结束标记。
2. Web容器解释执行到自定义标签的结束标记时,就会调用标签处理器的doEndTag方法,doEndTag方法执行完后可以向Web容器返回常EVAL_PAGE或SKIP_PAGE。如果返回常EVAL_PAGE,Web容器就会接着执行JSP页面中位于结束标记后面的JSP代码;否则Web容器就会忽略JSP页面中位于结束标记后面的所有内容,例如,<jsp:forward>标准标签会导Web容器忽略JSP页面中位于该标签后面的内容,这正是基于这个原理的一种具体应用。
从doStartTag和doEadTag方法的作用和返回值的作用可以看出,开发自定义标签时可以在doStartTag方法和doEndTag方法体内编写合适的Java程序代码来实现具体的功能,通过控制doStartTag方法和doEndTag方法的返回值,还可以告诉Web容器是否执行自定义标签中的标签体内容和JSP页面中位于自定义标签的结束标记后面的内容。
IterationTag接口
IterationTag接口继承了Tag接口,并在Tag接口的基础上增加了一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。实现IterationTag接口的标签除了可以完成Tag接口所能完成的功能外,还能够通知Web容器是否重复执行标签体内容。对于实现了IterationTag接口的自定义标签,Web容器在执行完自定义标签的标签体后,将调用标签处处理的doAfterBody方法,doAfterBody方法可以向Web容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果返回EVAL_BODY_AGAIN,Web容器就会把标签体内容再重复执行一次,执行完后接着再调用doAfterBody方法,如此往复,直到doAfterBody方法返回常量SKIP_BODY, Web容器才会开始处理标签的结
束标记和调用doEndTag方法。
可见,开发自定义标签时,可以通过控制doAfterBody方法的返回值来告诉Web容器是否重复执行标签体内容,从而达到循环处理标签体内容的效果。例如,可以通过一个实现IterationTag接口的标签来对一个集合中的所有元素进行迭代,在标签体中输出每次迭代到的元素。
在JSP API中提供了一个IterationTag接口的实现类TagSupport,用户开发实现Tag或IterationTag接口的标签时,可以继承这个类来简化自己的开发工作。注意:TagSupport类实现的doStartTag方法的返回值为SKIP_BODY,即标签处理器直接继承TagSupport类的doStartTag方法时,Web容器将忽略标签体的内容,而不向浏览器输出标签体内容,TagSupport类实现的doAfterBody方法的返回值为SKIP_BODY,doEndTag方法的返回值为EVAL_PAGE。
BodyTag接口
BodyTag接口继承了IterationTag接口,并在IterationTag接口的基础上增加了两个方法(setBodyContent, doInitBody)和一个EVAL_BODY_BUFFERED常量(注,EVAL_BODY_TAG常量已被废弃)。实现BodyTag接口的标签除了可以完成IterationTag接口所能完成的功能,还可以对标签体内容进行修改。对于实现了BodyTag接口的自定义标签,标签处理器的doStartTag方法不仅可以返回常量 EVAL_BODY_INCLUDE或SKIP_BODY,还可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED, Web容器就会创建一个专用于捕获标签体运行结果的BodyContent对象,然后调用标签处理器的setBodyContent方法将BodyContent对象的引用传递给标签处理器,Web容器接着将标签体的执行结果写入到BodyContent对象中。在标签处理器的后续事件方法中,可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。
JSP API中提供了BodyTag接口的实现类BodyTagSupport,用户开发实现BodyTag口的标签时,可以继承这个类简化自己的开发工作。BodyTagSupport类实现的doStartTag方法的返回值为EVAL_BODY_BUFFERED,Web容器将创建一个BodyContent对象,并按照BodyTag接口的执行顺序依次调用标称签处理器的方法。与TagSupport类一样,doAfterBody方法的返回值为SKIP_BODY,doEndTag方法的返回值为EVAL_PAGE。
doStartTag、doAfterBody、doEndTag返回值总结
自定义标签的基本应用
Tag接口
javax.servlet.jsp.tagext.Tag接口是所有传统标签的父接口,它定义了JSP页面与标签处理器之间的基本通信规则。JSP引擎将JSP页面中的自定义标签翻译成Servlet的源文件时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。Tag接口中定义了如下一些方法:setPageContext、setParent、doStartTag、doEndTag、release,JSP按照这些方法的排列顺序对它们依次进行调用。
setPageContext(PageContext pc):Web容器实例化标签处理器后,将调用标签处理器类的setPageContext方法,将代表JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。
setParent(Tag t):Web容器调用标签处理器类的setPageContext方法之后,接着将调用标签处理器类的setParent方法将当前标签的父标签对应的标签处理器对象传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
int doStartTag():Web容器调用了setPageContext方法和setParent方法之后,接着将调用所设置的各个标签属性对应的setter方法,然后调用标签处理器中的doStartTag方法。doStartTag方法执行完后返回一个int类型常量,当返回EVAL_BODY_INCLUDE时,Web容器会接着执行自定义标签的标签体,当返回SKIP_BODY时,Web容器不执行标签体,而是直接调用代表结束标记的doEndTag方法。
int doEndTag():Web容器执行完自定义标签的标签体后,接着就会去调用标签处理器中的doEndTag方法。doEngTag方法执行完后返回一个int类型常量,当返回EVAI_PAGE时,Web容器会接着执行JSP页面中位于结束标记后面的其他JSP代码,当返回SKIP_PAGE时,结束标记后面的所有JSP代码都不会被Web容器执。
release(): JSP规范定义Web容器必须在标签处理器对象被垃圾回收器回收之前调用其release方法,以便自定义标签在该方法中释放其所占用的相关资源。但release方法的具体调用时间通常由具体的Web容器厂商决定,也就是说Web容器执行完自定义标签后可以暂时不调用它的release方法。
控制是否执行标签体内容
public class DisplayUserInfoTag extends TagSupport
{
public int doStartTag() throws JspException
{
HttpSession session = pageContext.getSession();
String username = (String)session.getAttribute("user");
if(username != null)
{
return EVAL_BODY_INCLUDE;
}
else
{
return SKIP_BODY;
}
}
}
<taglib>
……
<tag>
<name>displayUserInfo</name>
<tag-class>org.it315.DisplayUserInfoTag</tag-class>
<body-content>JSP</body-content>
</tag>
</taglib>
<it315:displayUserInfo>
<br>您的姓名是:${user}
</it315:displayUserInfo>
控制是否执行JSP页面的内容
public class ValidateTag extends TagSupport
{
public int doEndTag() throws JspException
{
HttpServletRequest req = (HttpServletRequest)pageContext.getRequest();
String referrer = req.getHeader("referer");
String sitePart = "http://" + req.getServerName();
if(referrer!=null && referrer.startsWith(sitePart))
{
return EVAL_PAGE;
}
else
{
try
{
pageContext.getOut().write("对不起,您的访问方式不合法!");
}
catch (IOException e)
{
e.printStackTrace();
}
return SKIP_PAGE;
}
}
}
<taglib>
……
<tag>
<name>validate</name>
<tag-class>org.it315.ValidateTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
<%@ taglib uri="/taglib" prefix="it315" %>
<it315:validate/>
如果您看到了这些内容,说明本JSP页面已经正常执行完毕!
自定义标签的属性
定义标签属性
1、 在标签处理器类中编写每个属性对应的setter方法
与JavaBean的属性定义方式相同。如属性名为url,则标签处理器必须提供一个与之对应的setUrl方法。在调用setParent()方法之后,在调用doStartTag()方法之前,依次调用标签设置的每个属性所对应的setter方法,将各个属性值传递给标签处理器类。
2、 在TLD文件中定义属性
<tag>
...
<attribute>
<name>attr1</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.languag.String</type>
</attribute>
</tag>
rtexprvalue元素用于指定属性值是一个静态值或动态值,默认为false,表示只能为该属性指定静态文本值。如果为true,属性值可以是动态的,如 <%= value%>。这些属性都不是必须的。
public class ValidateTag extends TagSupport {
private String url;
public void setUrl(String url) {
this.url = url;
}
public int doEndTag() throws JspException {
HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
String referrer = req.getHeader("referer");
String sitePart = "http://" + req.getServerName();
if (referrer != null && referrer.startsWith(sitePart)) {
return EVAL_PAGE;
} else {
try {
pageContext.forward(url);
} catch (Exception e) {
e.printStackTrace();
}
return SKIP_PAGE;
}
}
}
<taglib>
……
<tag>
<name>validate</name>
<tag-class>org.it315.ValidateTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>url</name>
<required>true</required>
</attribute>
</tag>
</taglib>
<%@ taglib uri="/taglib.tld" prefix="it315" %>
<it315:validate url="visitor.html"/>
如果您看到了这些内容,说明本JSP页面已经正常执行完毕!
动态属性
动态属性是指定在标签处理器类和TLD文件中都没有预先声明的属性,但是在JSP页面中却可以为标签设置这些属性。
实现步骤:
1、 让标签处理器实现javax.servlet.jsp.tagext.DynamicAttributes接口。
2、 在TLD文件中使用<dynamic-attributes>元素声明标签支持动态属性。
DynamicAttributes接口只有一个方法:
setDynamicAttribute(String uri, String localName, Object value)
JSP引擎在处理自定义标签的每个动态属性时,都会调用setDynamricAttribute方法,把该属性的信息传递给标签处理器。SetDynamicAttribute方法中的参数uri指定了属性的名称空间,参数localname指定了属性名称(即属性自身的名字),参数value指定了属性的设置值。
JSP 2.0在TLD文件的<tag>元素中定义了一个子元素<dynamic-attributes>,它有true和false两个可选值,true表示标签支持动态属性,false表示不支持,默认认值为false。注意,在标签的TLD文件中设置<dynamic-attributes>元素值为true后,标签处理器就必须实现DynamicAttributes接口,否则编译器会报错。
自定义标签支持动态属性的好处在于,JSP页面编写人员可以根据实际的运行环境为标签动态增加属性,例如,假设有一个自定义标签用于生成HTML的下拉列表框,列表项由JSP页面编写人员通过标签属性来指定,在JSP页面中为标签设置的每个属性分别对应列表框中的一个列表项,这个自定义标签就需要支持动态属性。
示例:利用自定义标签的动态属性生成下拉列表框
public class ListTag extends TagSupport implements DynamicAttributes {
private String name;//下拉框名
private HashMap map = new HashMap();//下拉框选项
public void setName(String name) {
this.name = name;
}
public void setDynamicAttribute(String uri, String localName, Object value)
throws JspException {
map.put(localName, value);
}
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();
Set list = map.entrySet();
try {
out.print("<select name='" + name + "'>");
Iterator it = list.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
out.print(" <option value='");
out.print(entry.getKey());
out.print("'>");
out.print(entry.getValue());
out.println("</option>");
}
out.print("</select>");
} catch (IOException e) {
e.printStackTrace();
}
return super.doStartTag();
}
}
<taglib>
……
<tag>
<name>list</name>
<tag-class>org.it315.ListTag</tag-class>
<body-content>empty</body-content>
<attribute>
<description>description</description>
<name>name</name>
<required>true</required>
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
</taglib>
<%@ page contentType="text/html;charset=gb2312" %>
<%@ taglib uri="/taglib" prefix="it315" %>
<p>请选择你喜欢的男歌星</p>
<it315:list name="singer" n1="刘德华" n2="张学友" n3="郭富城" n4="黎明"/>
生成的HTML如下:
<select name='singer'>
<option value='n3'>郭富城</option>
<option value='n1'>刘德华</option>
<option value='n4'>黎明</option>
<option value='n2'>张学友</option>
</select>
迭代标签
IterationTag接口就是在Tag接口的基础上增加了doAfterBody方法,并根据doAfterBody方法的返回值(EVAL_BODY_AGAIN或SKIP_BODY)来决定是否再次执行标签体内容,直到doAfterBody方法返回常量SKIP_BODY止。
迭代标签通常要有两个属性,一个属性用于指定所要迭代的集合对象,另外一个属性用于指定迭代变量的名称(即在标签体中可以通该名称引用存储在域对象中所对应的属性),如下:
<it315:iterate name="bookname" items="<%= books%>">
${bookname}
</it315:iterate>
示例:
public class IterateTag extends TagSupport {
String name;
String[] items;
int i = 1;
public void setName(String name) {
this.name = name;
}
public void setItems(String[] items) {
this.items = items;
}
public int doStartTag() throws JspException {
if (items != null && items.length > 0) {
pageContext.setAttribute(name, items[0]);
return EVAL_BODY_INCLUDE;
} else {
return SKIP_BODY;
}
}
public int doAfterBody() throws JspException {
if (i < items.length) {
pageContext.setAttribute(name, items[i]);
i++;
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
}
<tag>
<name>iterate</name>
<tag-class>org.it315.IterateTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>name</name>
<required>true</required>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<%@ page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="/taglib" prefix="it315"%>
<%
String[] books = { "Java就业培训教程", "JavaScript网页开发",
"深入体验Java Web开发内幕", "Java邮件程序开发详解" };
%>
<it315:iterate name="bookname" items="<%= books %>">
<!--<%=pageContext.findAttribute("bookname")%><br/>-->
${bookname}<br />
</it315:iterate>
结果:
Java就业培训教程
JavaScript网页开发
深入体验Java Web开发内幕
Java邮件程序开发详解
注意,下面如果不关闭页面重新刷新页面时,你会发现只输出第一项,后三项却没有了,这正是容器默认使用了自定义标签处理器缓存技术,所以i被保留下来刷新后继续使用导致刷新后只显示一项,所以我们要在迭代处理完成后要将变量i复原,所以最好重写 doEndTag 方法:
public int doEndTag() throws JspException {
i = 1;//迭代完成后复位,以便下次缓存共享
return super.doEndTag();
}
这法就可以解决这缓存问题了。
自定义标签的运行原理
原码请参见 控制是否执行JSP页面的内容,Jsp所生成的Servlet文件如下:
public final class ViewIP_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static java.util.List _jspx_dependants;
static {
_jspx_dependants = new java.util.ArrayList(1);
_jspx_dependants.add("/WEB-INF/jsp2-example-taglib.tld");
}
private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspInit() {
_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody = org.apache.jasper.runtime.TagHandlerPool
.getTagHandlerPool(getServletConfig());
}
public void _jspDestroy() {
_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody.release();
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response) throws java.io.IOException,
ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=gb2312");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("\r\n");
out.write("<H1>鎮ㄧ殑IP鍦板潃鏄細");
if (_jspx_meth_it315_005fviewIP_005f0(_jspx_page_context))
return;
out.write("</H1>");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null)
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
private boolean _jspx_meth_it315_005fviewIP_005f0(
PageContext _jspx_page_context) throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// it315:viewIP
org.it315.ViewIPTag _jspx_th_it315_005fviewIP_005f0 = (org.it315.ViewIPTag) _005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody
.get(org.it315.ViewIPTag.class);
_jspx_th_it315_005fviewIP_005f0.setPageContext(_jspx_page_context);
_jspx_th_it315_005fviewIP_005f0.setParent(null);
int _jspx_eval_it315_005fviewIP_005f0 = _jspx_th_it315_005fviewIP_005f0
.doStartTag();
if (_jspx_th_it315_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody
.reuse(_jspx_th_it315_005fviewIP_005f0);
return true;
}
_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody
.reuse(_jspx_th_it315_005fviewIP_005f0);
return false;
}
}
生成的Servlet源码中,定义了一个_jspx_meth_it315_005fviewIP_005f0方法,容器在遇到自定义标签时就调用这个方法,该方法逻辑是这样的:
l 该方法首先从一个_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody对应中获得标签处理器的实例对象,_005fjspx_005ftagPool_005fit315_005fviewIP_005fnobody对应是一个标签池对象,它用于缓存标签处理器。通过标签池对象获取标签处理器实例时,它首先查找池对象中是否缓存了该标签处理器实例,如果是,则返回该实例给调用者,否则新创建一个处理器存储在池中并返回。
l 根据JSP规范定义的顺序依次调用标签处理器中的 setPageContext、setParent、setUrl、doStartTag和doEndTag等方法。
l 注意,标签处理器的doEndTag方法执行完后,release方法并没有被立即调用。这是因为Tomcat在执行自定义标签时应用了标签池技术缓存标签处理器,所以没有立即调用。而是在_jspDstroy方法中调用了。
include指令与标签引用的文件中自定义标签中SKIP_PAGE影响
(1)使用include指令(被引入的页面被单独看用一个Servlet处理)引入JSP文件时,如果子页面中的自定义标签的doEndTag方法返回SKIP_PAGE,不仅子页面中位于自定义标签后的JSP代码不会执行,父页面中的后面部分的JSP代玛也不会执行。
(2)使用<jsp:include>标签引入JSP文件时,如果doEndTag方法返回SKIP_PAGE,只会造成子页面中位于自定义标签后的JSP代玛不会执行。
自定义标签的缓存问题和线程安全问题
并发访问标签处理器时,JSP引擎会产生一个新的标签处理器对象供新的线程使用,这说明任何时候标签处理器对象只在一个线程(即一个请求中,一个请求就是一个线程,当一个请求结束后再能被另一请求所共享)中运行。
在JSP规范的定义中,标签处理器类的实例对象在使用完毕后返回池中,可以被后继其他不同的JSP页面使用。
JSP规范中,要求标签处理器是线程安全的,所以我们不用担心线程安全性问题。但要注意的是,虽然是线程安全的,即同一标签处理器实例不会在同时被多个请求使用,但是可以在不同时间被同一页面(通过刷新)或其他页面(重新打开浏览器)共享,所要注意共享出现的问题,请参见 迭代标签示例部分的所出现的问题。
在JSP规范中对应release方法的定义是:Web容器必须保证在java的垃圾回收器回收标签处理器对象前调用它的release方法,以释放占用的相关资源。也就是规范中并没明确地说明何时调用,只是在回收前调用就可以了。所以在Tomcat中,在Servlet对象被销毁前调用了。
在conf\web.xml中配置是否使用自定义标签缓存:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>enablePooling</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
用自定标签定义JSP脚本变量
脚本变量:
<%
int x = 5 ;
%>
定义后使用脚本变量:
<%= x %>
<%= x %>与 ${x}的区别:前才是一个脚本变量,要“先定义,后使用”,而后者是从Web域中查找一个名称为x的属性,相当于<%=pageContext.findAttribute("x")%>。
自定义标签不仅仅是提供一个标签处理器供JSP页面调用,它还具有告诉引擎在生成Servlet源码时生成一段JSP脚本代码的功能,其中包括生成新的JSP脚本变量,不过需要在TLD文件中配置。
下面来让自定义标签生成一个JSP脚本变量,为JSP所用:
<taglib>
……
<tag>
<name>iterate</name>
<tag-class>org.it315.IterateTag</tag-class>
<body-content>JSP</body-content>
<!--
<variable>元素用于定义脚本变量,
它的子元素<name-given>用于指定变量的名称。
-->
<variable>
<name-given>bookname</name-given>
</variable>
<attribute>
……
</attribute>
</tag>
</taglib>
上在<variable>元素就是用来在自定义标签生成Servlet源码时自动生成一个JSP脚本变量。
<%@ taglib uri="/taglib" prefix="it315"%>
<%
String[] books = { "Java就业培训教程", "JavaScript网页开发",
"深入体验Java Web开发内幕", "Java邮件程序开发详解" };
%>
<it315:iterate name="bookname" items="<%= books %>">
<!-- 以下三种都可以 -->
<!--%=pageContext.findAttribute("bookname")%><br/>
${bookname}<br/-->
<%=bookname%><br/>
</it315:iterate>
JSP引擎在翻译该自定义标签时,会生成如下代码:
String bookname = null;
bookname = (java.lang.String)pageContext.findAttribute("bookname");
在TLD文件中定义JSP脚本变量
可以在TLD文件中使用<tag>元素的子元素<variable>定义JSP脚本变量,<variable>元素属性位于<tag>内的<body-content>元素之后和<attribute>元素之前,可以出现任意次,每一个<variable>元素都分别定义一个JSP脚本变量。<variable>元素有<description>、<name-given>、<name-from-attribute>、<variable-class>、<declare>和<scope>,其中<name-given>和<name-from-attribute>两个子元素不能同时出现,这些子元素还必须按照出现:
<name-given>或<name-from-attribute>:二选其一,但必须指定。其中<name-given>元素指定一个固定的脚本变量名;<name-from-attribute>表示JSP脚本变量名来自于指定的标签属性的值,JSP引擎在声明脚本变量时,将使用该属性的值作为脚本变量名。
<variable-class>:可选,用于指定脚本变量的Java类型,默认值是java.lang.String。
<declare>:可选,默认为true。JSP引擎在处理<variable>元素声明的脚本变量时,在JSP页面翻译成的Servlet源文件中通常要做两件事:一是产生该JSP脚本变量的声明语句,二是为该脚本变量赋值。declare元素用于指定JSP引擎在处理declare元素声明的脚本变量时,是否生成JSP脚本变量的声明语句。true表示创建JSP脚本变量的声明语句。false表示不创建。如果在JSP页面中已经定义过该JSP脚本变量,自定义标签只是要将Web域中的同名属性赋值给该变量,则应该将declare元素设置为false。
<scope>:可选,默认为NESTED。三个可选值:NESTED、AT_BEGIN、AT_ENG。NESTED表示定义的JSP脚本变量可以在标签的开始和结束标记之间使用。AT_BEGIN表示定义的JSP脚本变量可以在开始标记到JSP页面结束之前都可以使用,AT_END表示只可以在结束标记到JSP页面结束之前可以使用。下面是范围示意图:
<tag>
<name>iterate</name>
<tag-class>org.it315.IterateTag</tag-class>
<body-content>JSP</body-content>
<variable>
<name-from-attribute>name</name-from-attribute>
</variable>
……
</tag>
<it315:iterate name="abc" items="<%=books %>">
<%= abc %><br/>
</it315:iterate>
用TagExtraInfo类定义JSP脚本变量
除了可以使用TLD文件定义JSP脚本变量外,还可使用java.servlet.jsp.tagext包中的TagExtraInfo类业定义JSP脚本变量。使用时还会用到TagData和VariableInfo这两个辅助类。
l VariableInfo
用于封装脚本变量的定义信息,并提供了JSP页面获得这些脚本变量的定义信息的方法。并提供了一个用于封装变量信息的构造函数:
VariableInfo(String varName, String className, boolean declare, int scope)
l TagData
主要用于获得标签的属性及属性值,其getAttributeString(String attName)方法以字符串形式获得指定属性的属性值。
l TagExtraInfo
引擎通过它可获得脚本变量的定义信息。它是一个抽象类,要覆盖 VariableInfo[] getVariableInfo(TagData data) 方法,在该方法内创建并返回封装了脚本变量信息的 VariableInfo 对象。
上面方法中的参数TagData对象中封装了标签的属性设置信息,JSP引擎在调用getVariableInfo方法时,会根据标签的属性自动构建出TagData对象提供给getVariableInfo方法使用,我们可以通过TagData对象的getAttributeString方法获得标签的某个属性的值,然后以该属性值作为要创建的JSP脚本变量名称、甚至是作为要创建的JSP标签变量的类型。
标签开发者创建的TagExtralInfo类也必须在TLD文件中对它进行注册,以便JSP引擎可以知道它的存在。TLD文件中的<tag>元索的<tei-class>元索用于把标签开发者编写的TagExtraInfo类注册到自定义标签中,<tei-class>元素位于<tag-class>元素之后和<body-content>元素之前,其描述格式如下黑体字部分所示:
<tag>
<name>tagname</name>
<tag-class>pack.MyTag</tag-class>
<tei-class>pack.MyTagExtraInfo</tei-class>
<body-content>content-type</body-content>
</tag>
示例:使用TagExtraInfo类定义脚本变量
package org.it315;
import javax.servlet.jsp.tagext.TagData;
import javax.servlet.jsp.tagext.TagExtraInfo;
import javax.servlet.jsp.tagext.VariableInfo;
public class MyTagExtraInfo extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData tagData) {
return new VariableInfo[] { new VariableInfo(tagData
.getAttributeString("name"),// 获得标签的name属性值
"java.lang.String", true, VariableInfo.AT_BEGIN) };
}
}
<tag>
<name>iterate</name>
<tag-class>org.it315.IterateTag</tag-class>
<tei-class>org.it315.MyTagExtraInfo</tei-class>
<body-content>JSP</body-content>
<attribute>
<name>name</name>
<required>true</required>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<%@ taglib uri="/taglib" prefix="it315"%>
<%
String[] books = { "Java就业培训教程", "JavaScript网页开发",
"深入体验Java Web开发内幕", "Java邮件程序开发详解" };
%>
<it315:iterate name="bookname" items="<%= books %>">
<!-- 以下三种都可以 -->
<!--%=pageContext.findAttribute("bookname")%><br/>
${bookname}<br/-->
<%=bookname%><br/>
</it315:iterate>
使用TLD文件和使用TagExtraInfo类定义脚本变量的区别
在TLD丈件中声明变量时可以通过标签的属性为变量命名,但变量的类型却只能在<variable-class>元素中硬性指定。这种情况下,我们无法开发出类似于<jsp:usebean id="" class="" scope="">这样的标签。因为<jsp:usebean>标签可以引用任何类型的java对象,所以标签开发者就无法提前确定<variable-class>元素的值,因此也就无法开发出这这种签。
由于TagExtraInfo类可以通过TagDaga对象获得标签的所有属性设置信息,我们可以通过标签的属性来设置要产生的JSP脚本变量的名称、类型和作用域等信息,然后,在TaqExtraInfo类中使用这些属性设置信息创建VariableInfo对象。所以,如果要使用自定义标签定义出任意类型的JSP脚本变量,就必须使用TagExtraInfo类。
示例:编写功能与<jsp:usebean>标签相似的自定义标签<it315:usebean>
public class UseBeanTag extends TagSupport {
private String name;// Bean的引用名
private String type;// Bean的类型
private String scope;// Bean存储的作用域
private int iScope;
public void setName(String name) {
this.name = name;
}
public void setType(String type) {
this.type = type;
}
public void setScope(String scope) {
this.scope = scope;
}
public int doStartTag() throws JspException {
if ("page".equals(scope)) {
iScope = pageContext.PAGE_SCOPE;
} else if ("request".equals(scope)) {
iScope = pageContext.REQUEST_SCOPE;
} else if ("session".equals(scope)) {
iScope = pageContext.SESSION_SCOPE;
} else if ("application".equals(scope)) {
iScope = pageContext.APPLICATION_SCOPE;
}
try {
// 实例化引入的类
Object obj = Class.forName(type).newInstance();
// 将实例化的Bean设置在相应的作用域对应内
pageContext.setAttribute(name, obj, iScope);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return EVAL_BODY_INCLUDE;
}
}
public class MyTagExtraInfo extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData tagData) {
return new VariableInfo[] { new VariableInfo(tagData
.getAttributeString("name"),// 获得标签的name属性值
tagData.getAttributeString("type"),// 获得标签的type属性值
true, VariableInfo.AT_BEGIN) };
}
}
<tag>
<name>usebean</name>
<tag-class>org.it315.UseBeanTag</tag-class>
<tei-class>org.it315.MyTagExtraInfo</tei-class>
<body-content>JSP</body-content>
<attribute>
<name>name</name>
<required>true</required>
</attribute>
<attribute>
<name>type</name>
<required>true</required>
</attribute>
<attribute>
<name>scope</name>
<required>true</required>
</attribute>
</tag>
<%@ taglib uri="/taglib" prefix="it315"%>
<%@ page contentType="text/html;charset=gb2312"%>
<%
request.setCharacterEncoding("gb2312");
%>
<it315:usebean name="date" type="java.util.Date" scope="page" />
当前日期:
<%=date.getYear() + 1900%>年<%=date.getMonth() + 1%>月<%=date.getDate()%>日
<!--Date的year属性是当前年份减去1900的结果,因此要加上1900得到实际的年份-->
自定义标签生命周期与执行流程图
处理标签体内容
BodyTag接口
BodyTag接口就是在IterationTag接口的基础上允许doStartTag方法返回常量 EVAL_BODY_BUFFERED,这个返回值将导致 Web容器创建一个javax.servlet.jsp.BodyContent对象后,再调用BodyTag.setBodyContent和BodyTag.doInitBody方法,并将标鉴体内容写入到通过setBodyContent方法传递进来的那个BadyContent流对象中,这样标签处器就可以控制标签体内容的输出,例如,将标签体内容输出给浏览器,或输出到命令行窗口中,或改变标签体内容后再输出。
setBodyContent(BodyContent b):用于将JSP引擎创建的BodyContent对象传递给标签处理器。
doInitBody():在setBodyContent方法之后被调用,它通常用于在Web容器执行标签体之前初始化BodyContent对象,可见,如果在doInitBody方法内向BodyContent对象中写入了一些内容,这些内容将出现在标签体的执行结果的前面。
BodyContent类
BodyContent是一个继承了JspWriter类的特殊输出流类,它在JspWriter的基础上增加了一个用于保存数据的缓冲区,当调用BodyContent对象的方法写入数据时,数据将被写入到BodyContent对象内部的数据缓冲区中。BodyContent类还定义了一些用于访问缓冲区内容的方法,这些方法可以将缓冲区内容转换成字符串或者写入到其他输出流中。我们可以在标签处理器的doEndTag方法中调用BodyContent对象的方法获得其内部缓冲区中的数据,然后对这些数据进行修改后输出给浏览器。
PageContext内部有一个“out”成员变量用于指向一个JspWriter对象或BodyContent对象(其实也是一种JspWriter对象),Pagecontext.getOut方法返回的结果正是这个“out”成员所指向JspWriter对象。如果标签处理器的doStartTag方法返回常 EVAL_BODY_BUFFERED, Web容器将调用PageContext类的pushBody方法产生一个BodyContent实例对象,PageContext.pushBody方法产生BodyContent实例对象后,将PageContext内部的“out”成员变量原来指向的JspWriter对象压入堆栈,并让PageContext内部的“out”成员变量指向这个新创建的BodyContent对象,所以,在这种情况下,在标签体中调用Pagecontext.getOut()方法返回的对象为这个新创建的BodyContent对象,而不是原来的“out”对象,标签体的内容就被写入到了这个新创建的BodyContent对象中。在Web调用doEndTag方法之前,Web容器又将调用PageContext.popBody方法从堆栈中弹出原来的JspWriter对象,并让Pagecontext内部的“out”成员变量重新指向弹出的JspWriter对象,以后在标签外面调用Pagecontext.getOut()方法得到的JapWriter对象又是原来的“out”对象了。
PageContext.pushBody方法产生BodyContent实例对象时,将会为新创建的BodyContent对象设置一个关联的JspWriter对象,这个关联的JspWriter对象通常为PageContext内部的“out”成员变量上次指向的JspWriter对象或者BodyContent对象,并且我们可以通过getEnclosingWriter方法来获得。
BodyContent类常用方法:
1、 BodyContent(JspWriter e),构造器,实例化BodyContent对象并设置其关联的JspWriter对象。
2、 String getString():以字符串形式返回BodyContent对象缓冲区保存的内容。
3、 Reader getReader():返回一个连接到BodyContent对象缓冲上区的java.io.Reader对象,通过这个Reader对象可以读取缓冲区中的内容。
4、 clearBody():用于清空BodyContent对象缓冲区中的内容。
5、 JspWriter getEnclosingWriter():用于得到BodyContent对象中关联的JspWriter对象。
6、 writeOut(Writer out):将BodyContent对象缓冲区中的内容写入到指定的输出流中。
示例:
public class BodyTagDemo extends BodyTagSupport {
public int doEndTag() throws JspException {
String content = bodyContent.getString();// 得到标签体内容
String newContent = "<a href='http://" + content + "'>" + content
+ "</a>";
// 得到BodyContent中关联的JspWriter对象
JspWriter out = bodyContent.getEnclosingWriter();
try {
out.write(newContent);
} catch (IOException e) {
e.printStackTrace();
}
return EVAL_PAGE;
}
}
由于只能在标签处理器的doAfterBody或doEndTag方法中才可从bodyContent对象中得到标签体的执行结果,所以,要想修改标签体的执行结果,只能在doAfterBody或doEndTag方法中编写处理代码。
由于只能在标签处理器的doAfterBody或doEndTag方法中才可从bodyContent对象中得到标签体的执行结果,所以,要想修改标签体的执行结果,只能在doAfterBody或doEndTag方法中编写处理代码。doEnd方法中直接使用了一个bodyContent对象,这是由于BodyTagSupport类的doStartTag方法默认返回EVAL_BODY_BUFFERED,Web容器将创建BodyContent对象,并通过调用标签处理器的setBodyContent方法将其传递给标签处理器,BodyTagSupport类中定义了一个protected类型的成员变量 bodyContext,并在其setBodyContent方法中将作为参数传递进来 BodyContent 对象赋值给了成员变量bodyContent。
<tag>
<name>testBody</name>
<tag-class>org.it315.BodyTagDemo</tag-class>
<body-content>JSP</body-content>
</tag>
<%@ taglib uri="/taglib" prefix="it315" %>
<%@ page contentType="text/html;charset=gb2312" %>
<it315:testBody>
localhost:8080/myapp/testBodyTag.jsp
</it315:testBody>
BodyTag工作原理
private boolean _jspx_meth_it315_005ftestBody_005f0(
PageContext _jspx_page_context) throws Throwable {
PageContext pageContext = _jspx_page_context;
//得到内置对象out流
JspWriter out = _jspx_page_context.getOut();
// it315:testBody 标签处理器实例对象
org.it315.BodyTagDemo _jspx_th_it315_005ftestBody_005f0 =
(org.it315.BodyTagDemo) _005fjspx_005ftagPool_005fit315_005ftestBody
.get(org.it315.BodyTagDemo.class);
//依次调用setPageContext、setParent、doStartTag方法
_jspx_th_it315_005ftestBody_005f0.setPageContext(_jspx_page_context);
_jspx_th_it315_005ftestBody_005f0.setParent(null);
int _jspx_eval_it315_005ftestBody_005f0 = _jspx_th_it315_005ftestBody_005f0
.doStartTag();
if (_jspx_eval_it315_005ftestBody_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {
if (_jspx_eval_it315_005ftestBody_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {
/*
* 当doStartTag方法返回EVAL_BODY_BUFFERED时,调用pageContext对象的
* pushBody方法获得BodyContent对象,并把原来指向JspWriter对象的out(注,
* 此out对象非彼out内置对象)
* 变量指向新创建的BodyContent对象。
*/
out = _jspx_page_context.pushBody();
/*
* 下面两行调用标签处理器的setBodyContent方法把BodyContent对象的引用
* 传递给标签处理器,并调用doInitBody方法完成初始化功能。
*/
_jspx_th_it315_005ftestBody_005f0
.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);
_jspx_th_it315_005ftestBody_005f0.doInitBody();
}
do {
//下面两行调用BodyContent的write方法将标签体内容写入到BodyContent对象中
out.write("\r\n");
out.write("\tlocalhost:8080/myapp/testBodyTag.jsp\r\n");
//标签体执行完后执行标签处理器的doAfterBody方法
int evalDoAfterBody = _jspx_th_it315_005ftestBody_005f0
.doAfterBody();
//循环执行标签体直接 doAfterBody 返回EVAL_BODY_AGAIN止
if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)
break;
} while (true);
//如果
if (_jspx_eval_it315_005ftestBody_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {
/*
* 标签处理器的doStartTag方法返回的是 EVAL_BODY_BUFFERED时,调用
* pageContext对象的popBody方法重新获得原来的JspWriter对象,并让
* out变量再次指向这个对象
*/
out = _jspx_page_context.popBody();
}
}
//调用标签处理器的doEndTag方法
if (_jspx_th_it315_005ftestBody_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
_005fjspx_005ftagPool_005fit315_005ftestBody
.reuse(_jspx_th_it315_005ftestBody_005f0);
return true;
}
_005fjspx_005ftagPool_005fit315_005ftestBody
.reuse(_jspx_th_it315_005ftestBody_005f0);
return false;
}
1、 BodyContent对象是Web容器调用pageContext.pushBody方法创建的,WEB容器创建BodyContent对象后,会把原来指向输出流对象JspWriter的引用句柄out(注,这个out对象不是内置的out对象,即内置out对象的指向还是没有改变)指向新创建的BodyContent对象,并调用setBodyContent方法将BodyContent对象的引用传递给标签处理器。因此,在标签体中调用out.write方法时,实际上是调用BodyContent对象的Write方法将内容写入到BodyContent对象中。
2、 只有执行完标签体后,BodyContent对象中才有标签体的执行结果内容,因此,如果要在标签处理器中操作标签体内容,只能在标签处理器doAfterBody和doEndTag方法体内进行。对于迭代标签,每次在doAfterBody方法中从bodyContent对象内获得的内容都不包括后面循环的执行结果,要想获得所有迭代循环的执行结果,只能在doEndTag方法中偏写处理代码。
3、 Web容器在调用标签处理器的doEndTag方法前,会调用pageContext对象的popBody方法恢复原来的JspWriter对象,并让引用句柄out(注,这里还是不是指内置对象的那个out,因为真真的内置对象out一直未修改过)再次指向这个对象。因此,在标签体外面调用out.wrtite方法时,内容又将写入到原来的JspWriter对象(通常是JSP页面的隐含out对象)中。
BodyTag编程实例
对HTML特殊字符进行转义的自定标签。下面除了编写对特殊字符进行转义的标签<it315:HtmlFilter>之外,还编写了一个专门用于读取文件内容的自定义标签<it315:readFile src= "">。
<%@ taglib uri="/taglib" prefix="it315"%>
<%@ page contentType="text/html;charset=gb2312"%>
<it315:HtmlFilter>
<it315:readFile src="/show-source.jsp" />
</it315:HtmlFilter>
public class HtmlFilterTag extends BodyTagSupport {
public int doEndTag() throws JspException {
if (bodyContent != null) {
String content = bodyContent.getString();
content = filter(content);// 调用filter方法对HTML标签进行转义
try {
// 下面两行等效,因为容器在调用标签处理器的doEndTag方法前,会调用
// pageContext对象的popBody方法恢复原来的JspWriter对象
// pageContext.getOut().write(content);
bodyContent.getEnclosingWriter().write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
return EVAL_PAGE;
}
public String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
public class ReadFileTag extends TagSupport {
private String src;
public void setSrc(String src) {
this.src = src;
}
public int doStartTag() throws JspException {
InputStream in;
BufferedReader br;
try {
in = pageContext.getServletContext().getResourceAsStream(src);
br = new BufferedReader(new InputStreamReader(in));
String line = br.readLine();
while (line != null) {
pageContext.getOut().write(line + "\r\n");
line = br.readLine();
}
br.close();
} catch (IOException ioe) {
ioe.getMessage();
}
return SKIP_BODY;
}
}
<tag>
<name>HtmlFilter</name>
<tag-class>org.it315.HtmlFilterTag</tag-class>
<body-content>JSP</body-content>
</tag>
<tag>
<name>readFile</name>
<tag-class>org.it315.ReadFileTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>src</name>
<required>true</required>
</attribute>
</tag>
tagdependent标签体类型
<body-content>元素可能指定empty、JSP、scriptless和tagdependent四种类型,其中tagdependent较难道理解,现以上面那个例子为例,将<it315:HtmlFilter>它的标签体类型设置为tagdependent。
修改后,发现显示直接将标签体作为普通内容输出了,而不是将标签体看作是嵌套标签了。显示的结果为:
<it315:readFile src="/show-source.jsp" />