SimpleTag是JSP2.0中新增的一个标签接口,由于传统标签使用三个标签接口来完成不同的功能,过于烦琐,因此,在JSP2.0中定义了一个更为简单的SimpleTag接口。它与传统标签接口的区别在于,它只定义了一个用于处理标签逻辑的doTag方法,该方法只被执行一次,那些传统标签接口所完成的功能,如是否执行标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。
简单标签的处理流程:
1、 引擎调用标签处理器的setJspContext(JspContext pc)方法,将pageContext对象传递给标签处理器对象(JspContext为PageContext的父类,是JSP2.0中新增的接口)。
2、 引擎调用标签处理器的setParent(JspTag parent)方法,将父标签处理器对象传递给这个标签处理器对象。注,只有在有父标签的情况下才调用,这与传统标签处理方式不同(JspTag为JSP2.0中新增的接口,它是传统标签接口Tag与简单标签接口SimpleTag接口的共同父接口)。
3、 如果调用标签时设置了属性,将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则引擎首先计算表达式的值,然后传递给标签处理器对象。如果标签支持动态属性,则JSP引擎调用DynamicAttributes.setDynamicAttribute方法为标签处理器对象设置每个动态属性。
4、 如果简单标签有标签体,容器将标签体封装成一个JspFragment对象,然后调用setJspBody(JspFragment jspBody)方法将这个JspFragment对象传递给标签处理器对象。如果标签体类型声明为空,则setJspBody方法不会被容器调用。
5、 容器调用标签处理器对象的doTag()方法执行标签逻辑(包括输出、迭代、修改标签体内容等)。
注:简单标签与传统标签除了所实现的接口不同之外,JSP规范还要求JSP引擎在每次处理JSP页面中的简单标签时,都需要创建一个独立的简单标签处理实例对象,而不会像传统标签那样对标签处理器对象进行缓存。
SimpleTagSupport类是为了简化简单标签处理器的实现。它实现了SimpleTag接口中的方法,它内部以成员变量的形式保存了setJspContext方法和setJspBody方法传递进来的参数。
为JSP2.0中定义的类。它代表JSP页面中的一段符合JSP语法规范的JSP片段,但这段JSP片段中不能包含JSP脚本元素。
JSP引擎在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。得到代表标签体的JspFragment对象后,标签开发者就可以在标签处理器中根据需要调用JspFragment对象的方法,以此来决定是否执行和输出标签体,或多次循环执行和输出标签体,或修改标签体的执行结果后再进行输出。
两种方法:
JspContext getJspContext()。
void invoke(Writer out) throws SkipPageException:其中参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果传递给参数out的值为Null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。在简单标签的doTag方法体内根据需要调用JspFragment.invoke方法,就可以和传统标签一样控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如,在标签处理器中根据某种判断条件而没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如,StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。
如果想不执行标签后面的内容,则要抛出SkipPageException异常。
<%@ taglib uri="/taglib" prefix="it315" %>
<%@ page contentType="text/html;charset=gb2312" %>
<H4>
<it315:simpleTag username="${param.user}">
xxxxxx<br/>
</it315:simpleTag>
<br/>这是标签后面的JSP页面......
</H4>
public class SimpleTagDemo extends SimpleTagSupport {
private String username;
public void setUsername(String username) {
this.username = username;
}
public void doTag() throws JspException, IOException {
if ("zxx".equals(username)) {
JspFragment fragment = this.getJspBody();
//调用JspFragment的invoke方法执行标签体
fragment.invoke(null);
} else {
getJspContext().getOut().write("用户名不正确");
//注,简单标签忽略标签后面的JSP内容则抛出SkipPageException即可
throw new SkipPageException();
}
}
}
<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>SimpleTag</short-name>
<uri>/taglib</uri>
<tag>
<name>simpleTag</name>
<tag-class>org.it315.SimpleTagDemo</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>username</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
在TLD中声明简单标签的方式与声明传统标签的方式类似,但是,简单标签的<body-content>元素值的可选范围为empty, scriptless或tagdependent,其默认值为scriptless。因为简单标签的标签体中不能包含JSP脚本元素(否则显示以下错误信息:Scripting elements ( <%!, <jsp:declaration, <%=, <jsp:expression, <%,<jsp:scriptlet ) are disallowed here),所以不能像传统标签那样将<body-content>元素的值指定为JSP。
<%@ page contentType="text/html;charset=gb2312"%>
<%@ taglib uri="/taglib" prefix="it315"%>
<%
String[] books = { "Java就业培训教程", "JavaScript网页开发",
"深入Java Web开发内幕", "Java邮件程序开发详解" };
%>
<it315:iterateTagDemo name="bookname" items="<%= books %>">
${bookname}<br />
</it315:iterateTagDemo>
public class IterateTagDemo extends SimpleTagSupport {
private String name;
private String items[];
public void setName(String name) {
this.name = name;
}
public void setItems(String[] items) {
this.items = items;
}
public void doTag() throws JspException, IOException {
// 循环执行标签体
for (int i = 0; i < items.length; i++) {
getJspContext().setAttribute(name, items[i]);
getJspBody().invoke(null);
}
}
}
<tag>
<name>iterateTagDemo</name>
<tag-class>org.it315.IterateTagDemo</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>name</name>
<required>true</required>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
该实例与BodyTag编程实例功能一样。
<%@ taglib uri="/taglib" prefix="it315"%>
<%@ page contentType="text/html;charset=gb2312"%>
<pre>
<it315:HtmlFilter>
<it315:readFile src="/show-source.jsp" />
</it315:HtmlFilter>
</pre>
public class ReadFileTag extends SimpleTagSupport {
private String src;
public void setSrc(String src) {
this.src = src;
}
public void doTag() throws JspException, IOException {
InputStream in;
BufferedReader br;
try {
in = ((PageContext) getJspContext()).getServletContext()
.getResourceAsStream(src);
br = new BufferedReader(new InputStreamReader(in));
String line = br.readLine();
while (line != null) {
// 输出到父标签提供的流中:this.getJspContext().getOut()为
// 父标签传进来的流对象
this.getJspContext().getOut().write(line + "\r\n");
line = br.readLine();
}
br.close();
} catch (IOException ioe) {
ioe.getMessage();
}
}
}
public class HtmlFilterTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
JspFragment fragment = this.getJspBody();
StringWriter writer = new StringWriter();
//执行标签体,并改变子标签的输出方向
fragment.invoke(writer);
//修改子标签的执行结果
String newbody = filter(writer.toString());
//将修改后的结果重新输出到浏览器
this.getJspContext().getOut().write(newbody);
}
public String filter(String message) {
//请参考BodyTag编程实例
}
}
<tag>
<name>HtmlFilter</name>
<tag-class>org.it315.HtmlFilterTag</tag-class>
<body-content>scriptless</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>
嵌套标签内的out对象不指向原来内置的out对象,上面ReadFileTag中的this.getJspContext().getOut().write(line + "\r\n");语句并没有直接输出到浏览器,这是为什么呢?
简单标签的标签体是用JspFragment对象表示的,如果父标签在标签处理器中没有调用代表子标签的JspFragment对象的invoke方法,那么于标签将不会执行,也就是子标签的实例对象不会被创建和被调用。如果父标签在调用代表子标签的fragment对象的invoke方法时,指定了一个输出流时象,例如JspFragment.invoke(new StringWriter()),那么,在子标签中调用this.getJspContext().getOut()方法所得到的writer对象将是一个新的JspWriter对象,这个JspWriter对象包装了传入的输出流对象,以传入的输出流对象作为底层输出目标。其内部原理是:在invoke方法内部,首先调用JspContext.pushBody方法创建出一个新JspWriter对象,这个新JspWriter对象以传递给invoke方法的writer对象作为包装目标,然后将JspContext内部的“Out”成员变量原来指向的JspWriter对象压入堆栈,并让JspContext(其实就是PageContext对象)内部“out”成员变量指向这个新创建的JspWriter对象,所以,在子标签中调用JspContext.getOut()方法返回的对象为这个新创建的JspWriter对象,而不是JSP页面中的内置out对象;在invoke方法返回前其内部又调用JspContext.popBody方法从堆栈中弹出原来的JspWriter对象,并让JspContext内部的“out”成员变量重新指向弹出的JspWriter对象,以后在父标签中调用JspContext.getOut()方法得到的JspWriter对象又是JSP页面中的内置out对象了。
JSP2.0允许开发人员使用标准标签<jsp:attribute>来为其他标签(传统标签可以吗?)设置属性,如:
<%@ taglib uri="/taglib" prefix="it315"%>
<%@ page contentType="text/html;charset=gb2312"%>
<jsp:useBean id="now" class="java.util.Date" />
<it315:fragmentDemo>
<jsp:attribute name="frag1">
<jsp:getProperty name="now" property="year" />年<br/>
</jsp:attribute>
<jsp:attribute name="frag2">
<jsp:getProperty name="now" property="month" />月<br/>
</jsp:attribute>
<jsp:body>
<jsp:getProperty name="now" property="date" />日<br/>
</jsp:body>
</it315:fragmentDemo>
上面的代码在<it315:fragmentDemo>标签中嵌套了一个<jsp:attribute>标签,这相当于为<it315:fragmentDemo>标签设置了两个属性,属性的名称为“frag1”与“frag2”,属性的值为<jsp:attribute>标签中的标签体内容。对于这种以<jsp:attribute>元素设置的属性,其属性值的类型为JspFragment,即JSP引擎将创建一个JspFragment对象来表示属性值,然后调用标签处理器中的相应setter方法把JspFragment对象传递给标签处理器对象。在标签处理器中,只需要调用这个代表属性值的JspFragment对象的invoke方法,就可以得到该属性值的最终结果。
由于自定义标签的属性值部分的语法格式有一定的要求,例如,属性值中不能再出现标签(如<it315:fragmentDemo src="<jsp:xxx/>">是不可以的),属性值中不能有回车换行等内容,<jsp:attribute>元素提供了一种将这些特殊内容设置给某个属性的手段,它可以把任意格式的一段JSP片段设置成某个标签的属性值。
注意,如果使用<jsp:attribute>标签为某个标签设置了属性,则该标签的标鉴体内容必须便用<jsp:body>标签来设里,否则,JSP引擎将会报错。
public class FragmentDemo extends SimpleTagSupport {
/*
* frag1/frag2分别用来接收标签属性frag1/frag2
* 传递进来的JspFragment对象
*/
private JspFragment frag1;
private JspFragment frag2;
public void setFrag1(JspFragment frag1) {
this.frag1 = frag1;
}
public void setFrag2(JspFragment frag2) {
this.frag2 = frag2;
}
public void doTag() throws JspException, IOException {
getJspContext().getOut().write("标签第一个属性的执行结果为:");
frag1.invoke(null);
getJspContext().getOut().write("标签第二个属性的执行结果为:");
frag2.invoke(null);
JspFragment fragment = getJspBody();
if (fragment != null) {
getJspContext().getOut().write("标签体内容为:");
getJspBody().invoke(null);
}
}
}
<tag>
<name>fragmentDemo</name>
<tag-class>org.it315.FragmentDemo</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>frag1</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<attribute>
<name>frag2</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
</tag>
TLD文件中的<fragment>元素是JSP2.0中定义的,它用于表示标签属性值的类型是否为JspFragment。如果该元素的值为true,则Web容器会用一个JspFragment对象来封装属性值,该元素的默认值为false。
1) 对于简单标签,Web容器不会直接执行标签体内容,而是调用简单标签处理器的setJspBody方法将简单标签的标签体内容以JspFragment对象的形式传递给标签处理器使用,再由简单标签的标签处理器在doTag方法内部进行处理和向浏览器输出标签体内容。而传统标签可以利用doStartTag方法的返回值告诉Web容器是否直接执行和输出标签体内容。因为JspFragment对象不能表示含有JSP脚本元素的JSP片段,所以,简单标签的标签体中不能包含JSP脚本元素,即<body-content>元素不能设里为JSP。
2) 传统标签可以利用doEngTag方法的返回值告诉Web容器是否执行标签后面的内容,简单标签只能通过在doTag方法中抛出SkipPageException异常,告诉Web容器终止JSP页面的运行。
3) 传统标签通过doAfterBody方法的返回值告诉Web容器是否重复执行标签体内容,以完成迭代功能;而简单标签是对得到的代表标签体内容的JspFragment对象进行循环调用,以实现迭代功能。
4) 传统标签对修改标签体的操作是通过一个BodyContent对象实现的,而简单标签是对JspFragment对象进行操作。
5) 简单标签和传统标签都可以通过实现javax.jsp.tagext.DynamicAtttibutes接口来支持动态属性。
由此可见,传统标签所能完成的功能,简单标签都可以完成(但有一点要注意的是,简单标签体里不能含有JSP脚本元素,如JSP表达式、JSP脚本、JSP声明这些元素,否则显示以下错误信息:Scripting elements ( <%!, <jsp:declaration, <%=, <jsp:expression, <%,<jsp:scriptlet ) are disallowed here)。简单标签的运行原理相对简单,可以大大降低用户开发标签时的难度。
容器在启动时,会自动为Web应用构建一个TagLib Map对象,用于存储URI与TLD资源文件路径的对应关系,TagLib Map对象构建过程如下:
l 从web.xml文件中查找<taglib>元素,将其子元素<taglib-uri>和<taglib-location>中的URI和TLD文件路径增加到TagLib Map对象中。<taglib-location>元素的值可以以“/”开头,也可以不以“/”开头,不以“/”开头表示相对于web.xml文件的路径。
l 从WEB-INF及其子目录中、以及WEB-INF/lib目录下的各个Jar包内的META-INF目录及其子目录中搜索TLD文件,如果TagLib Map对象中还不存在TLD文件中的<uri>元素的值,则将这些TLD文件中的<uri>元素的值和TLD文件的路径位置增加到TagLib Map对象中。
容器在解析执行JSP页面中的某个标签时,它首先得到的taglib指令的uri属性所指定的值,然后按照下面的顺序查找该值所对应的TLD文件:
l 如果uri属性的值是一个绝对路径(以协议名和主机名开头),则从Taglib Map对象中查找,找不到就报错。
l 如果uri属性的值是以“/”开头的相对路径,则从Taglib Map对象中查找,如果找不到,就直接把这个URI当做TLD资源文件路径处理。
l 如果uri属性的值不是以“/”开头的相对路径,则从Taglib Map对象中查找,如果找不到,就以当前JSP页面的路径来计算这个相对路径,并将计算结果当做TLD资源文件路径处理。
Servlet 2.4规范中定义了一个<jsp-config>元素,在<jsp-config>元素中可以使用<taglib>元素设置tld文件的映射URI,例如可以在web.xml文件中采用如下方式进行部署:
<jsp-config>
<taglib>
<taglib-uri>http://www.it315.org/taglib</taglib-uri>
<taglib-location>/WEB-INF/simpleTaglib.tld</taglib-location>
</taglib>
</jsp-config>
部署后可以在JSP中如下引用标签:
<%@ taglib uri="http://www.it315.org/taglib" prefix="it315"%>
注:在Servlet 2.3规范中,<taglib>元素是web.xml文件根元素<web-app>的直接子元素;而在Setvlet2.4规范中,<taglib>元素是<web-app>的子元素<jsp-config>的直接子元素。
我们还可以将 *.tld 文件放入到与标签处理器类所在的 xx.jar 包中的 META-INF这个文件中。
为了简化自定义标签的开发,甚至让完全不懂Java编程的网页开发人员也可以开发可复用的自定义标签库,SUN公司在JSP 2.0中定义了一种新的标签库开发技术——标签文件。标签文件是以“.tag”或“.tegx”作为文件后缀名的纯文本文件,它采用和JSP相似的语法规则,标签开发人员使用标签文件开发自定义标签时,无须编写用作标签处理器的Java类,只需像编写JSP页面一样编写标签文件,并将标签文件放置在指定位置即可。
创建和使用标签文件需完成如下几个步骤:
1、 编写用于完成某项功能的标签文件,如下面的显示当前日期的标签文件 today.tag:
<jsp:useBean id="now" scope="page" class="java.util.Date" />
${now.year+1900}-${now.month+1}-${now.date}
2、 在Web应用中部署标签文件
JSP 2.0规范定义了下面两种标签文件的部署方式:
1) 把标签文件放置到/WEB-INF/tags/目录或者其子目录下(此时的 tld 文件放在 /WEB-INF/目录下的除lib和classess外的目录或子目录下),一个目录中可以放置多个标签文件,放置标签文件的每一个目录就相当于一个标签库,JSP引擎会为每个标签目录产生一个隐含的TLD文件,目录中的每个标签文件都对应一个标签,将标签文件名除去扩展名的部分即为标签名称。
2) 把标签文件放置到WEB-INF/lib目录中的一个JAR文件的META-INF/tags/目录或其子目录中(此时的 tld 文件放在相应的 JAR文件包中的META-INF或子目录中)。对于这种方式,JSP规范规定必须在TLD文件中显式地注册标签文件。JSP 2.0在TLD文件中定义了<taglib>元素的直接子元素<tag-file>,用于描述标签文件。<tag-file>元素有两个必需的子元素<name>和<path>,其中<name>元素用于指定标签名称,<path>元素用于指定标签文件的位置。如tagfile.tld:
<taglib ...>
<description>tagfile</description>
<tlib-version>1.0</tlib-version>
<short-name>tagfile</short-name>
<uri>/tagfilelib</uri>
<tag-file>
<name>today</name>
<path>/META-INF/tags/today.tag</path>
</tag-file>
</taglib>
3、 在JSP页面中使用taglib指令引用标签文件
对于上面两种部署标签文件的方式,在JSP文件中的引用方式也不相同:
1) 当把标签文件部署在 WEB-INF/tags目录下时,在JSP页面中必须使用taglib指令的tagdir属性指定标签文件所在的目录,而不是使用taglib指令的uri属性来指定标签库的URI。如:
<%@page pageEncoding="GB2312"%>
<%@ taglib prefix="it315" tagdir="/WEB-INF/tags" %>
当前日期:<it315:today/>
2) 当把标签文件放置在Jar包中的META-INF/tags/目录或其子目录下时,由于必须在TLD文件中注册标签文件,因此在JSP页面中与调用普通标签的方式完全一样,也是使用taglib指令的uri属性来指定标签库的URI。
标签文件的运行原理:JSP引擎在解释执行标签文件时会把它翻译成一个实现了SimpleTag接口的标签处理器类。
标签文件采用的语法规则和JSP文件类似。
1) JSP 2.0为标签文件定义了tag、attribute和variable三条新的指令,它们只能在标签文件中使用。在标签文件中不能使用page指令,它通过tag指令的一些属性来实现Page指令的相应属性的功能,这包括pageEncoding、import、isELIgnored等属性,tag指令中还定义了一些用于对标签进行描述的属性,例如,用于实现类似tld文件中的<tag>元素的一些设置信息,包括body-content、dynamic-attributes等属性。另外标签文件中还可以通过使用variable和attribute指令来完成定义标签属性和变量的功能。
2) JSP 2.0定义了两个只能在标签文件中使用的标准标签<jsp:doBody>和<jsp:invoke>。其中<jsp:doBody>标签用于执行标签体部分的内容,<jsp:invoke>标签用于执行以标签属性传递进来的JSP片段。
在标签文件中可以使用attribute指令来为标签定义属性,JSP页面可以通过设置标签的属性来将信息传递给标签文件。attribute指令的作用类似于TLD文件中的<attribute>元素,该指令中定义了多个属性来对定义的标签属性进行描述。使用attribute指令定义标签属性的例子如下:
<%@ attribute name="x" required="true" fragment="false"
rtexprvalue="false" type="java.lang.String"
description="The first operand" %>
attribute指令中的name、required等属性所代表的含义和TLD文件中的<attribute>元素的<name>、<required>等同名子元素所代表的含义完全一致。在标签文件中使用attribute指令定义属性后,JSP引擎在翻译标签文件时,就会在生成的标签处理器类中加上相应的setter方法,例如,上面示例会生成 setX与getX,并将setter方法传递进来的属性值保存到了以属性名命名的成员变量中和pageContext对象中,这样,在标签文件中既可以使用脚本变量访问传递进来的属性值,也可以使用EL表达式访问传递进来的属性值。
示例:
================== WEB-INF/tags/toUpper.tag ==============
<%@ attribute name="str" %>
<%
//这个str是一个成员变量,它记住了setStr方法传递进来的参数值
str = str.toUpperCase();
out.println(str);
%>
================== toUpper.jsp ==============
<%@ taglib prefix="it315" tagdir="/WEB-INF/tags" %>
<it315:toUpper str="hello it315"/>
variable指令只能用在标签文件中,它的作用等同于TLD文件中的<variable>元素,用于在JSP页面中定义脚本变量,并把标签文件中产生的数据提供给JSP页面使用:
<%@ variable name-given="sum" variable-class="java.lang.Integer"
scope="NESTED" declare="true" description="The sum of the two operands"%>
variable指令中的各个属性的含义和TLD文件中的<variable>元素的同名子元素的含义一致,请参看这里。
在标签文件中使用variable指令定义变量后,JSP就会产生如下动作:
1) 在翻译标签文件的调用页面时,在Servlet中定义一个相应的变量。
2) 把标签文件pageContext中的属性同步到调用页面的pageContext属性中。
3) 在调用页面中取出pageContext中变量所对应的属性,并用变量指向取出的属性值。
注意以上过程,在标签文件中使用variable指令和在TLD文件中用variable元素定义,JSP引擎在进行处理时,其处理过程是有差别的,差别如下:
l 前面提到过,自己编写的标签处理器类和其调用页面使用的是同一个pageContext对象,在标签处理器类中往pageContext中保存的域属性,可以在JSP页面中使用EL表达式直接得到,JSP引擎无须执行上面第2步的同步操作。
l 向标签文件的pageContext中保存的域属性,如果在标签文件中没有使用variable指令进行声明,则该作用域中保存的属性无法在JSP页面中使用EL表达式得到。其原因在于标签文件使用的pageContext对象是其调用页面的pageContext对象的包装类,和调用页面使用的并不是同一个对象,因此在JSP页面中无法使用EL表达式得到标签文件的pageContext中保存的域属性。但如果我们在标签文件中使用variable指令对该属性进行声明后,JSP引擎在执行JSP页面中的EL表达式前,将会执行一个同步操作,把标签文件中的pageContext属性同步到调用页面的pageContext中。这样,在调用页面中就可以使用EL表达式获得标签文件的pageContext中的属性了。
示例:
=============== variableTag.jsp ===============
<%@ taglib prefix="it315" tagdir="/WEB-INF/tags" %>
<%
pageContext.setAttribute("x","Hello IT315!");
%>
在tag文件中取属性x的值为:<it315:variableTag/>
=============== /WEB-INF/tags/variableTag.tag ===============
${x}
=============== 页面输出结果 ===============
在tag文件中取属性x的值为:
从结果可以看到,在标签文件中没有得到JSP页面保存在pageContext中的属性“x”的值,可见,标签文件和其调用页面使用的并不是同一个pageContext域对象。下面这样修改后还是不能显示,这进一步证实了这个结论:
=============== variableTag.jsp ===============
<%@ taglib prefix="it315" tagdir="/WEB-INF/tags" %>
<it315:variableTag/>
在tag文件中取属性x的值为:${x}
=============== /WEB-INF/tags/variableTag.tag ===============
<%
jspContext.setAttribute("x","Hello IT315!");
%>
如果在variableTag.tag标签文件中加上一条件定义变量x的variable指令后,再重新访问,则可正常显示:
<%@ variable name-given="x" scope="AT_BEGIN" %>
<%
jspContext.setAttribute("x","Hello IT315!");
%>
页面输出结果:在tag文件中取属性x的值为:Hello IT315!
以上实验过程证明了标签文件和其调用页面使用的并不是同一个pageContext域对象,保存在JSP页面的pageContext域中的域属性,在标签文件中无法得到;在标签文件的pageContext域中保存的域属性,在JSP页面中也无法直接得到。为实现标签文件向其调用页面传递数据之目的,JSP规范定义了一个variable指令,在tag文件中使用该指令对某个pageContext域属性进行了声明,那么web容器在执行标签文件时,将会执行一个同步操作,把保存在标签文件的pageContext域中的属性同步到其调用页面的pageContext域中。
在JSP页面中可以使用JSP标准标签<jsp:attribute>来为其他标签设置JspFragment类型的属性,如果被调用的标签是来用标签文件的方式编写出来的,那么在标签文件中如何来处理这种JspFragment类型的属性昵?在标签文件中除了可以通过JSP脚本片段来引用和执行通过属性传递进来的JspFragment对象外,还可以使用<jsp:invoke>标签引用和执行通过属性传递进来的JspFragment对象。
假设<it315:fragdemo>是一个用标签文件编写的标签,这个标签定义了一个frag属性,在JSP页面中使用<jsp:attribute>将一段JSP代码片段设置给这个标签的frag属性,如:
<%@ page pageEncoding="GB2312"%>
<%@ taglib prefix="it315" tagdir="/WEB-INF/tags" %>
<it315:fragdemo>
<jsp:attribute name="frag">
Jsp代码片段,但不能包括JSP脚本元素
</jsp:attribute>
</it315: fragdemo>
<jsp:attribute>标签中嵌套的JSP代码片段会被JSP引擎包装成一个JspFragment对象,然后作为标签属性值传递给标签文件,在标签文件中使用attribute指令声明该属性时,必须将attribute指令的fragment属性设置为true,然后可以按例下面两种方式引用和执行通过该属性传递进来的JSP代码片段:
第一种执行方式:
<%@ attribute name="frag" fragment="true" %>
<%
frag.invoke(null);
%>
第二种执行方式:
<%@ attribute name="frag" fragment="true" %>
<jsp:invoke fragment="frag"/>
此标签只能用在标签文件中,用于执行JSP页面中设置的标签体内容,三个属性:
如果在tag文件中没有调用<jsp:doBody>标签,那么,在JSP页面中调用标签时,标签体中的内容不会被执行和输出,与没有标签体时的情况完全一样。在标签文件中调用<jsp:doBody>标签时,如果不指定var或varReader属性,代表标签体的JspFragment对象的执行结果就会输出到jspContext对象的getOut()方法返回的输出流对象中(通常对应到览器),如果指定了 var或varReader属性,则代表标签体的JspFragment对象的执行结果将会保存到一个字符串或java.io.Reader类型的对象中,而不会输出到jspContext对象的.getOut()方法返回的输出流对象中,程序可以从字符串或Reader对象中读取致据并进行修改后再输出到“out”对象中,即可达到修改标签体运行结果的目的,这和自己编写简单标签处理器时修改标签体的方式类似。
由JSP页面提供数据,标签文件生成显示单元。
==============/WEB-INF/tags/panel.tag===============
<%@ attribute name="color" %>
<%@ attribute name="bgcolor" %>
<%@ attribute name="title" %>
<table border="1" bgcolor="${color}">
<tr>
<td><b>${title}</b></td>
</tr>
<tr>
<td bgcolor="${bgcolor}">
<jsp:doBody/>
</td>
</tr>
</table>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<html>
<head>
<title>JSP 2.0 Examples - Panels using Tag Files</title>
</head>
<body>
<h1>JSP 2.0 Examples - Panels using Tag Files</h1>
<table border="0">
<tr valign="top">
<td>
<tags:panel color="#ff8080" bgcolor="#ffc0c0" title="Panel 1">
First panel.<br/>
</tags:panel>
</td>
<td>
<tags:panel color="#80ff80" bgcolor="#c0ffc0" title="Panel 2">
Second panel.<br/>
Second panel.<br/>
Second panel.<br/>
Second panel.<br/>
</tags:panel>
</td>
<td>
<tags:panel color="#8080ff" bgcolor="#c0c0ff" title="Panel 3">
Third panel.<br/>
<tags:panel color="#ff80ff" bgcolor="#ffc0ff" title="Inner">
A panel in a panel.
</tags:panel>
Third panel.<br/>
</tags:panel>
</td>
</tr>
</table>
</body>
</html>
上面的例子由JSP页面提供数据,标签文件生成显示单元。我们也可以在JSP页面中编写显示单元,由标签文件提供数据信息和选择显示单元。
这两个表格显示的数据内容相同,但表现形式不同。每个表格中显示的商品都分为打折和不打折两种类别,不打折商品只显示一个价格,而打折商品显示有两个价格。用于显示打折商品和不打折商品的代码片段在JSP页面中定义,tag文件的作用则是获取商品的信息,然后根据商品是否为打折商品再调用JSP页面中定义的代码片段进行显示。
在tag文件中怎样调用JSP页面中定义的代码片段呢?有两种方式:一种方式是把代码片段作为标签体内容,然后在tag文件中调用<jsp:doBody>标签执行代码片段;另一种方式是把代码片段作为JspFragment类型的属性传递给标签,然后在tag文件中调用<jsp:invoke>标签执行代码片段。由于采用标签体的形式只能提供一个代码片段,如果tag文件中要执行多个代码片段,则必须采用多个JspFragment类型的属性来提供多个代码片段。
================= products.jsp=================
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<html>
<head>
<title>JSP 2.0 Examples - Display Products Tag File</title>
</head>
<body>
<h2>Products</h2>
<tags:displayProducts>
<jsp:attribute name="normalPrice">
Item: ${name}<br/>
Price: ${price}
</jsp:attribute>
<jsp:attribute name="onSale">
Item: ${name}<br/>
<font color="red"><strike>Was: ${origPrice}</strike></font><br/>
<b>Now: ${salePrice}</b>
</jsp:attribute>
</tags:displayProducts>
<hr>
<h2>Products (Same tag, alternate style)</h2>
<tags:displayProducts>
<jsp:attribute name="normalPrice">
<b>${name}</b> @ ${price} ea.
</jsp:attribute>
<jsp:attribute name="onSale">
<b>${name}</b> @ ${salePrice} ea. (was: ${origPrice})
</jsp:attribute>
</tags:displayProducts>
</body>
</html>
“normalPrice”属性对应于不打折商品的显示样式,“onSale”属性对应于打折商品的显示样式
================= WEB-INF/tags/displayProducts.tag =================
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ attribute name="normalPrice" fragment="true" %>
<%@ attribute name="onSale" fragment="true" %>
<%@ variable name-given="name" %>
<%@ variable name-given="price" %>
<%@ variable name-given="origPrice" %>
<%@ variable name-given="salePrice" %>
<table border="1">
<tr>
<td>
<c:set var="name" value="Hand-held Color PDA"/>
<c:set var="price" value="$298.86"/>
<jsp:invoke fragment="normalPrice"/>
</td>
<td>
<c:set var="name" value="4-Pack 150 Watt Light Bulbs"/>
<c:set var="origPrice" value="$2.98"/>
<c:set var="salePrice" value="$2.32"/>
<jsp:invoke fragment="onSale"/>
</td>
</tr>
</table>