1999年12月,SUN公司发布《JSP 1.1规范》。至今,成千上万的开发者运用此项技术,进行Web动态内容设计。 SUN的众多邮件列表中,《JSP爱好者》拥有4000多订户,位居第二;每天都有新的JSP网站涌现;至今已有约30种JSP图书问世。JSP 1.1 得到绝大多数Web服务器和应用服务器的支持;无数的JSP标签库和框架程序,构成开放式和商业化资源,为开发者提供了便利。 正当世界各地的开发者们为基于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,它们是标签开发者喜爱的好工具 |