正当世界各地的开发者们为基于JSP 1.1的新应用而忙碌时,负责制定JSP规范的组织JCP(Java Community Process),推出了《JSP 1.2规范》(代号JSR-053)。JCP的成员包括规范作者,应用服务器、JSP和服务器小程序的开发者,他们来自大小小各个公司商家,也有的是开放式资源的参与者。
《JSP 1.2规范》9月17日公布,现已准备广泛实施。JSP 1.2新增若干功能,纠正了低版本的瑕疵。其最重要的变化是:
JSP 1.2 建立在 Servlet 2.3 和 Java 2 的基础上;.
include 可单独使用,无需设置flush 刷新属性;
JSP 页的 XML 语法结构完成定型;
标签(Tag)库可以利用 Servlet 2.3 的事件监听机制;
JSP页的有效性验证,增加新方式;
标签库的分发置放,增加若干新选择项式。
标签增加 2 个新接口(interface);
String 类型的文本标签,其属性值可以转换成 Object 类型;
PropertyEditor 可以转换用户设定的属性值;
标签明确了存续周期;
标签库描述符增设新元素,与 J2EE 的其他描述符合并。
Tomcat 4.0 的实现,参照了 JSP 1.2 和 Servlet 2.3 两个规范。Tomcat服务器是基于 Java 的Web容器,用于运行 Servlet 和 JSP 程序。Tomcat 4.0 与《JSP 1.2规范》已同期发布,您可尝试其各种新鲜功能。有些商品化的Web容器,如新亚特兰大公司的ServletExec 4.1,也已支持JSP 1.2 和 Servlet 2.3。
本文将全面介绍JSP 1.2的新功能及其如何使用。大多数新功能涉及JSP程序员和Web容器开发者,读者应当熟知《JSP 1.1规范》。您若主要关心网页制作,恐怕不会对本文有太多兴趣,但仍可肯定,您会从JSP 1.2获益,譬如:功能更强大的自定义标签库,效率更高的Web容器,以及各种Web容器之间更好的兼容性等等。
Servlet 2.3 和 Java 2
JSP 1.2是根据最新版本《Servlet 2.3规范》制定的。因而,JSP 1.2程序能利小用服务器程序(servlet)的全部新增功能,如经过改进的监听器、过滤器、国际化转换器等。以下,我将说明如何使用标签库中的监听器。怎样利用 Servlet 2.3的其他新增功能,可阅读JavaWorld网站发表的有关Servlet 2.3的文章,作者是杰森?亨特。
JSP 1.2 和 Servlet 2.3 需在JAVA 2平台上运行。令人欣慰的是,一些Web容器,已可利用自JDK 1.1以来新增的全部功能,如收集器、更强的类装载器、灵活的安全机制等,当然,还有您拥有的相应的WEB应用程序。不过,也有美中不足,JSP 1.2应用程序不能运行在仅仅支持JDK 1.1的平台上。好在这样的平台眼下已经寥寥无几,我们中的绝大多数人不必为此烦心。
JSP 1.2向后兼容JSP 1.1。符合《JSP 1.2规范》的Web容器,运行JSP 1.1程序不应遇到不兼容的难题。
Include操作不必设置flush 刷新属性
JSP 1.1强制设置flush刷新属性值为true。因此,当实施include操作时,浏览器中看到的网页内容被刷新,不能再向其转发别的网页,也不能设置"响应"操作的引导信息。由此产生许多混乱和副作用,难以理解和处理。从Sun公司《JSP爱好者》邮件列表文档,可得到关于这个问题的详细说明。
JSP 1.2按照Servlet 2.3的要求,彻底消除这一限制,include命令的flush属性可以设置为false。事实上,flush属性现在是可选择而非强制性的,默认值为false,因而,尽可将其冷落不顾。
JSP 1.1页中像下面的语句,在include操作后执行forward操作,会产生错误。
<jsp:include page="common.jsp" flush="true" />
<% if (someCondition) { %>
<jsp:forward page="another.jsp" />
<% } %>
但是,根据《JSP 1.2规范》,无论将flush的值设为true或false,这些语句都是合法有效的。
甚至,您可以自行定义操作命令标签,将include命令置入其中。
<xsl:apply xsl="style.xsl">
<jsp:include page="someXML.jsp" />
</xsl:apply>
这些语句,因其缓存问题JSP 1.1视为非法,而JSP 1.2认定合法,因为缓存问题已经解决。
JSP页,JSP文档和XML视图
XML是JSP 1.2的重要成员。符合《JSP 1.2规范》的Web容器接受的文件,必须是符合《JSP 1.1规范》的JSP页;或者是遵从XML新格式的JSP文档。
JSP文档有一root元素用作<jsp:root>,定义JSP页的标准元素和自定义标签库的格式表(namespace)。JSP的全部命令和标注元素,必须以XML元素表示,取代原先JSP页的旧式元素:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:demo="/demolib"
version="1.2">
<jsp:directive.page contentType="text/html" />
<ul>
<demo:myLoopTag items="myCollection" var="current">
<li>
<jsp:getProperty name="current" property="lastName" />,
<jsp:getProperty name="current" property="firstName" />
</li>
</demo:myLoopTag>
</ul>
</jsp:root>
如上例所示,JSP基本元素如说明、自定义操作等,均可包含XHTML之类的XML元素。例中的<ul>和<li>即是。
受篇幅所限,本文不能介绍JSP文档的全部语法。注意,JSP文档的语法比JSP页的语法复杂得多,并且,其主要目的是用于各种工具。您若坚持使用这一复杂语法,请阅读《JSP 1.2规范》第5篇"JSP文档"。
Web容器,第一次收到JSP页的请求时,将其转变成类似JSP文档的XML文档,然后检验其是否合法,最后转换为小服务程序。这种XML文档,正规称呼叫做"JSP页的XML视图"。XML视图与JSP文档的唯一区别是:模板化的文本全部包括 在<jsp:text>元素中,并以VDATA部件防止文本特殊字符可能带来的问题。例如:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:demo="/demolib"
version="1.2">
<jsp:directive.page contentType="text/html" />
<jsp:text><![CDATA[<ul> ]]></jsp:text>
<demo:myLoopTag items="myCollection" var="current">
<jsp:text><![CDATA[<li> ]]></jsp:text>
<jsp:getProperty name="current" property="lastName" />
<jsp:getProperty name="current" property="firstName" />
</demo:myLoopTag>
<jsp:text><![CDATA[</ul> ]]></jsp:text>
</jsp:root>
以下还会介绍在XML视图中用TagLibraryValidator检验JSP页的合法性。
标签库分发放置的新方式
JSP 1.2 对标签库分发放置的方式做了一些简化,可以从单独的JAR文件自动查找、分置标签库。
自动查找标签库
JSP 1.1规定,使用标签库时,应当将库操作命令的属性uri设定为标签库所在真实路径,或者指定一个代号名称。如果使用代号名称,必须编辑该项应用的web.xml文件,将此代号名称对应的真实路径,写入<taglib>元素。
JSP 1.2 给出了可供选用的、功能更强的第三种方法:将标签库JAR文件存放于WEB-INF/lib目录,访问时使用标签库操作命令的标准URI地址。
以下是一例示。标签库描述符(TLD)包括一个<uri>元素,定义该库的标准URI地址:
<taglib>
...
<uri>/demo</uri>
...
</taglib>
Web应用程序启动后,Web容器开始搜索WEB-INF目录,查找全部标签库 .tjd文件。标签库 .tjd文件可以单独存在,也可以包含在JAR文件中META-INF目录中。
一个JAR文件中存放多个标签库
自动搜索机制有一大便利,即可以在同一JAR文件中存放多个标签库。
JSP 1.1规定,JAR文件中的标签库描述符TLD必须以META-INF/taglib.tld命名,因此,一个JAR文件只能包含一个TLD,即一个标签库。
JSP 1.2则可以将任何标签库 .tld文件置入JAR文件的META-INF目录内,作为一个TLD。可以把多个TLD和相应的 .class文件,置入同一JAR文件。这就方便了置放标签库。注意,必须使用自动查找机制置放包含多个标签库的JAR文件,因为,对于这种JAR文件无法指定单独TLD路径。0
标签库事件监听
《Servlet 2.3规范》扩展了先前版本的事件监听机制。此前,只能监听会话(session)属性变化,现在则可以监听:小服务程序和会话存续期间的各种事件,小服务程序环境属性的变化,会话开始和暂停事件。会话开始和暂停事件的发生,是由于Web容器将会话状态存于硬盘,或者将会话转至其他服务器。
各种新型事件监听器,源自Java事件模型。监听器是类(class),它实现一个或多个新式监听器接口interface。这些接口定义了响应事件的方法。Web应用程序启动时,Web容器注册它的事件监听器,并适时调用响应事件的方法。
可以将一个或多个事件监听器置入标签库。为了事件监听器能注册,应将实现监听器的类名,写入标签库描述符(TLD)的新元素<listener>:
<listener>
<listener-class>com.foo.MyListener</listener-class>
</listener>
Web容器装载Web应用程序后,会查看全部TLD找出监听器定义,将其注册。
事件监听器可用于多种任务。例如,小服务程序周期监听器,可以在应用程序(如连接池)启动时,为其初始化资源;在其停止运行时,将其关闭。会话周期监听器,可以初始化新会话,或者跟踪会话的数目。
下面是会话周期监听器的示例,它对进行中的会话保持跟踪。
package com.foo;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent hse) {
int[] counter = getCounter(hse);
counter[0]++;
}
public void sessionDestroyed(HttpSessionEvent hse) {
int[] counter = getCounter(hse);
counter[0]--;
}
private int[] getCounter(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
ServletContext context = session.getServletContext();
int[] counter = (int[]) context.getAttribute("com.foo.counter");
if (counter == null) {
counter = new int[1];
context.setAttribute("com.foo.counter", counter);
}
return counter;
}
}
会话任务数目增加时,小服务程序的环境属性作为计数器,其值增大;会话结束后,半数器值减小。标签可用于简单显示活动会话任务的数目。当活动的或者预定的会话任务达到一定数目,可用标签拒绝建立新会话。
检验器
标签库检验器是JSP 1.2新增重要功能。它的实现,基于类javax.servlet.jsp.tagext.TagLibraryValidator。Web容器
用检验器确定标签库中TLD的信息(强制属性值和空体)是否合法,然后将相应的JSP页转换成小服务程序。应将检验器说明为TagLibraryValidator的子类,并且重设其方法validate():
public ValidationMessage[] validate(String prefix,
String uri, PageData pageData)
检验器的validate()方法,由PageData的实例调用。检验器由此获得相应JSP页的XML表达式,即JSP页格式表的 XML视图或者JSP文档(即符合JSP页XML语法的标注)。读完JSP页的XML表达式后,检验器核查TLD信息是否可用,或者核查用户定义的操作元素的信息是否符合类TagExtraInfo的要求。例如,用户定义的A操作必须用作B操作的子元素,检验器可以核查是否使用了B,或者A、B是否以适当的顺序使用。
与检验器相关联的是标签库TLD的新增元素<validator>:
<validator>
<validator-class>com.foo.MyValidator</validator-class>
</validator>
元素<validator-class>指定检验器名称;选择性元素<init-param>为指定的标签库检验器作配置。
检验器的执行
举例说明。在同一标签库中有操作元素<redirect>,其中只能用一个<param>作为子元素。下面开始通过各段代码看检验器是是如何工作的:
package com.foo;
import java.util.*;
import javax.servlet.jsp.tagext.*;
import org.jdom.*;
import org.jdom.input.*;
public class MyValidator extends TagLibraryValidator {
private SAXBuilder builder = new SAXBuilder();
private Namespace jspNamespace = Namespace.getNamespace("jsp",
"http://java.sun.com/JSP/Page");
您已看到,检验器继承了JSP API的TagLibraryValidator。我用JDOM建立这个检验器,处理JSP页的XML表达式。 JDOM包中定义了解析JDOM树的类。当然,您也可以选用其他XML解析器和检验工具,就象标签库项目Jakarta的专家那样,用许多不同的XML工具构建检验器。
我创建了JDOM SAXBuilder类的实例,作为内存变量。如果Web容器中可以存储检验器的实例,我就不必再为各个JSP页逐一创建检验器。我还为JSP的格式表创建了JDOM Namespace的实例变量。下面再对其详细介绍。
检验器必须重设validate()方法:
public ValidationMessage[] validate(
String prefix, String uri, PageData pd) {
ValidationMessage[] vms = null;
ArrayList msgs = new ArrayList();
try {
Document doc = builder.build(pd.getInputStream());
Element root = doc.getRootElement();
validateElement(root, prefix, msgs);
}
catch (Exception e) {
vms = new ValidationMessage[1];
vms[0] = new ValidationMessage(null, e.getMessage()); }
if (msgs.size() != 0) {
vms = new ValidationMessage[msgs.size()];
msgs.toArray(vms);
}
return vms;
}
用validator()方法取得JSP页中的XML表达式,用JDOM解析JSP文档。然后,调用validateElement(root, prefix, msgs)方法。root是JSP文档的根本元素,prefix用于标签库,msgs是列表数组用于收集出错信息。如果validateElement()方法发现错误,消息列表便转换成ValidationMessage类型的数组,作为方法调用返回值。
稍后您会看到,ValidationMessage的实例含有出错信息,及其可能出自JSP页原文何处的信息。用数组反映全部JSP页的错误,便于作者集中更改,不必一次又一次的反复纠错。
validateElement()方法,用于调度那些检验指定元素的方法:
private void validateElement(Element e, String ns, ArrayList
msgs) {
if (ns.equals(e.getNamespace().getPrefix())) {
if (e.getName().equals("param")) {
validateParam(e, ns, msgs);
}
}
if (e.hasChildren()) {
List kids = e.getChildren();
Iterator i = kids.iterator();
while(i.hasNext()) {
validateElement((Element) i.next(), ns, msgs);
}
}
}
它是递归方法,供JSP文档树中各元素调用。首先,它检查当前的元素是否列在标签库格式表中,然后检查它是否需要检验,如是,则它调用相关方法处理。
本例中,我只检验各种元素的param类型是否合法,但您可以考虑如何扩展该方法,检验其他元素。
包含子结点的各种类型的元素,子结点也分别调用validateElement()方法,于是,它就递归地扫描整个JSP文档树:
private void validateParam(Element e, String ns,
ArrayList msgs) {
Element parent = findParent(e, ns, "redirect");
if (parent == null) {
String id = e.getAttributeValue("id", jspNamespace);
ValidationMessage vm = new ValidationMessage(id,
e.getQualifiedName() +
" must only be used with redirect");
msgs.add(vm);
}
}
validateParam()方法调用findParent()方法,检查当前param元素有无redirect类型的父元素。若无,则该param元素用法错误,于是,创建ValidationMessage实例报告出错,并记入错误信息列表。
ValidationMessage包含两条信息:出错信息和相关元素的专用代号。该专用代号由Web容器赋予,并且作为元素标识名称属性,jsp:id,列入JSP格式表,告知检验器。
validateParam()方法发现出错时,首先取得该标识属性,进而将其记入ValidationMessage。此项操作要用到先前提到的Namespace实例变量。
Web容器维护着标识位置对应列表。该列表反映着元素标识与该元素在JSP源文件中的行、列位置的对应关系。借助对应列表生成的出错信息,能指出错误位置,便于查纠。但要注意,Web容器不需要加入此标识。譬如,Tomcat 4.0 不支持这项功能,或许未来版本支持。
最后,findParent()方法的内容如下:
private Element findParent(Element e, String ns, String name) {
if (e.getName().equals(name) &&
ns.equals(e.getNamespace().getPrefix())) {
return e;
}
Element parent = e.getParent();
if (parent != null) {
return findParent(parent, ns, name);
}
return null;
}
它简单地递归调用,直到发现指定元素,或者递归到JSP文档树顶端。若发现匹配的元素,将其返回,否则,返回mull。
JSP也支持用户扩充新的检验机制,即通过TagExtraInfo类的isValid()方法,检验自己定义专用操作元素。以此方法做出的检验,与上述检验器相比,功能十分有限。既便如此,也比不检验强。
结 论
本文介绍了《JSP 1.2规范》的一些新功能,它们的确会增强您的应用程序。您可以完整利用Java 2 平台和Servlet 2.3 应用程序接口(API)的优势;更加灵活地使用include操作;开发标签库而无需额外配置;以事件监听器简化程序和会话状态管理;创建标签库检验器,全面检验JSP页是否合法,并且得到清晰实用的错误信息。
不过,一切才刚刚开始。本文的第二部分,将详细介绍自定义标签的API,它们是标签开发者喜爱的好工具。
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/