java WEB开发国际化(二)
这篇文章是讲述加入到JavaServer Pages (JSP) 2.0 规范中的特性的系列文章的第二部分。在前面的第一部分,我描述了新的EL表达式,但是还有更多的内容没有涉及。这一部分描述的是JavaServer Pages (JSP) 2.0 规范在错误处理机制和新的部署描述符特性方面的增强。我假设你熟悉JSP 1.2,而且至少听说过JSP Standard Tag Library (JSTL)。
JSP Error Pages
如果你曾经在JSP和servlet的错误处理中使用过JSP error page,并且想要显示或者记录违例信息(违例导致JSP error page 被调用),那么你就知道在JSP1.2中这并不是件轻松的事情。原因是当在servlets和JSP 页面中声明了一个errorPage,违例(exception)被作为一个request attribute传递,它们要使用不同的属性名称。只有被传递的违例通过JSP属性名称自动地显示在 JSP error page中(通过exception脚本变量和${pageContext.exception} EL 表达式)。
JSP 2.0通过将相同的属性名称转换为servlet规范中的javax.servlet.error.exception来修正了这个问题。更进一步说,一个新增的 命名为errorData的EL pageContext变量揭露了发生问题的其他信息。ErrorData属性是javax.servlet.jsp.ErrorData的一个实例。这个实例可以被 用来作为一个bean和以下的properties一同使用:
Property
Java 类型
描述
requestURI
String
发生请求失败的 URI
servletName
String
发生错误的servlet或者JSP页面的名称
statusCode
int
发生错误的状态码
throwable
Throwable
导致当前error page被调用的违例
这里有个JSP error page的例子。这个例子使用了上面提到的一些property:
<%@ page isErrorPage="true" contentType="text/html" %>
<%@ taglib prefix="log" uri="http://jakarta.apache.org/taglibs/log-1.0" %>
Sorry, but things didn‘t work out as planned. I‘ve logged as much as
I know about the problem, so rest assured that my master will look
into what‘s wrong as soon as he‘s sober.
<jsp:useBean id="now" class="java.util.Date" />
<log:fatal>
-----
${now}
Request that failed: ${pageContext.errorData.requestURI}
Status code: ${pageContext.errorData.statusCode}
Exception: ${pageContext.errorData.throwable}
-----
</log:fatal>
这个页面使用Apache Jakarta Taglib项目中的 Log tag library来显示一些确定的信息并记录下了具体的细节。
你可以在web.xml文件中使用<error-page>来对servlet和JSP 页面声明这是一个error page:
...
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error.jsp</location>
</error-page>
<error-page>
<exception-code>500</exception-code>
<location>/error.jsp</location>
</error-page>
...
如果你需要在某些特殊的JSP页面中使用一个不同的error page,可以用“@page”设置errorPage属性来有选择性地覆盖web.xml中的声明。
JSP 语法错误的报告
在JSP 1.2 和 JSP 2.0之间,一个细微但是重要的区别是JSP 2.0需要JSP容器支持“jsp:id”特性,虽然在JSP 1.2中这仅仅是一个建议。作为一个JSP开发者,这对你意味着什么呢?对于JSTL和定制的标签库的语法错误,它意味着你可以更好地获得有用的错误信息。
下面讲述它是如何工作的。当JSP容器将JSP页面转换为可以执行的servlet class,容器着眼于这个页面中声明的所有标签库。如果标签库中的一个或者多个库包含了一个标签库验证器(Tag Library Validator,TLV),容器就会在接受这个JSP页面前给予TLV一个检验当前页面的机会。容器给予TLV一个当前页面的XML视图用以分析。XML视图就像名称暗示的那样,是当前页面的另外一种版本, 所有常规的JSP元素和template text已经被转换为一种结构良好的XML文档。这个XML视图对TLV来说是很容易解析的,用于确定所有的custom actions被正确地使用(比如:custom actions被正确的嵌套,互斥的属性是不能在同一个action element中一同使用的)。
这就是“jsp:id”的由来。容器在页面中给每一个custom action元素分配了一个ID,并且维护了一张在ID和元素位置(文件、行号、列号)的映射表。添加的“jsp:id”属性,在XML视图中ID值对应于所有的custom action 元素,这样TLV就可以读取它。如果TLV发现了一个语法错误,它就会在返回给容器的错误信息中包含“jsp:id”属性值来确定当前无效的action element。这时容器使用映射,并添加发生错误的custom action element位置信息,提示给你。这使得开发人员找到并改正错误变得轻而易举。
所有的JSTL库都要有TLV。我强烈建议为你自己编写的类库开发TLV,而且任何第三方类库的开发人员也应该这样做。
如果你对XML视图和TLV并不熟悉,我在2001年的介绍JSP 1.2的文章中有一个简要的介绍("JSP 1.2: Great News for the JSP Community")。
JSP 部署描述符
JSP 2.0在web.xml文件中使用了servlet规范中定义的部署描述符的文件格式,就像早期的JSP规范中的那样。然而在JSP 2.0中有两个重要的变化:1、现在web.xml文件的格式是通过XML Schema定义的;2、为了最大限度地减少servlet和JSP规范之间的耦合度,大多数的JSP配置细节已经被移到一个新的XML element中。
XML Schema是一种用来描述XML文档语法规则的XML语言(可以定义XML element之间是如何嵌套的;一个element可以获得什么样的值; 值的唯一性的需求等等)。这是一个复杂的规范,但是幸运的是你不用为了写web.xml文件而需要明白XML Schema语法规则。因为servlet和JSP规范提供了易于理解的图表( JavaServer Pages, 3rd Edition书中包含了许多简单易懂的图表)。如果你还是想要更进一步了解XML Schema,请浏览W3C(http://www.w3c.org)的网站。
用XML Schema代替上一版本中的 Document Type Definition(DTD) 语言来声明XML文档结构的主要优点是:XML Schema具有更加富有表达能力,因此在解析web.xml文件的过程中能发现更多的错误,有希望解决在JSP容器之间更好地移植这个问题。
另外一个优点是(我确定你会感激于此的): 在web.xml文件中使用XML Schema可以使顶层的element按照任何顺序排列变得轻而易举。举例来说,在servlet和JSP 规范的上一个版本中,如果将<error-page> element 放到<welcome-file-list> element 前面,你将得到一个错误提示。在新版本的规范中,这样做就不会有问题。在顶层元素中的element的顺序尽管还必须要按照严格的顺序放置,但是在顶层元素以外至少你现在可以*地支配了。
除了<jsp-file>要嵌套在<servlet>中,其它的element现在都被归组到一个新的顶层element中,这个element命名为<jsp-config>。在<jsp-config>中,你可以使用<taglib> element,和在JSP 1.2中具有相同的语法和含义,尽管它们并不需要实现了JSP 1.2 或者后续版本的容器,因为它们可以自动地从已经部署的JAR文件中获得标签库的定义。
新添加的<jsp-property-group>子元素更加有趣。你可以用它来配置一组匹配某个指定的URL的JSP页面。比如:
...
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>
...
<url-pattern>元素确定这组JSP 页面应用哪一个配置。其他被嵌套的元素定义配置选项。这个例子中, <scripting-invalid>使应用JSP scripting elements (Java code) 在所有的JSP 页面中无效。
总地来说同<scripting-invalid>一样,你可以在<jsp-property-group>中使用以下的配置元素:
Element
描述
<el-ignored>
如果设置为true, 在匹配指定的URL模式的JSP 页面中,EL 表达式被当作常规的文本而不是EL 表达式。当移植看起来有EL表达式文本的JSP 1.2页面到JSP 2.0的时候,这非常有用。在已经转换为JSP 2.0的页面中,你可以使用一个新增的“elIgnoredpage”属性来选择EL是否有效
<scripting-invalid>
如果设置为true, 在某个匹配的JSP页面使用脚本
<page-encoding>
为匹配指定的URL模式的JSP页面指定编码。这是一个可选的配置(在每一个JSP页面中指定页面的编码),并且对于JSP页面来说这是使用某些文件编码(比如:EBCDIC)唯一的办法
<include-coda>
为所有匹配指定的URL模式的JSP页面的末尾自动包含一个文件,而指定相关的context路径。你可以在一个<jsp-property-group>中多次使用,或者通过多个<jsp-property-group>来实现
<include-prelude>
为所有匹配指定的URL模式的JSP页面的开头自动包含一个文件,而指定相关的context路径。你可以在一个<jsp-property-group>中多次使用,或者通过多个<jsp-property-group>来实现
<is-xml>
如果设置为true, 所有匹配指定的URL模式的JSP页面使用JSP XML语法(它们是JSP Document)
总结
在这部分,我讲述了JSP 2.0增强的错误处理机制和新的部署描述符的特性。在这个系列文章中,接下来的部分将涉及到JSP 2.0是如何使JSP操作XML变得轻松,还有与定制标签库相关的新特性。
JSP 2.0: The New Deal, Part 3
by Hans Bergsten, author of JavaServer Pages, 3rd Edition
04/21/2004
更具伸缩性的JSP Document格式规则
JSP 2.0 规范支持两种类型的JSP页面:一种是包含任何数据类型的常规JSP页面;另一种是具备良好结构的XML文档(具有XHTML和JSP元素)。为了做到这一点,在一个JSP Document中JSP的 “directive” 和 “脚本”必须用一种与常规的JSP页面不同的语法来编写:
常规的 JSP 页面
JSP 文档
<%@ page attribute list %>
<jsp:directive.pageattribute list />
<%@ include file="path" %>
<jsp:directive.include file="path" />
<%! declaration %>
<jsp:declaration>declaration</jsp:declaration>
<%= expression %>
<jsp:expression>expression</jsp:expression>
<% scriptlet %>
<jsp:scriptlet>scriptlet</jsp:scriptlet>
在一个JSP Document中,标签库作为XML名称空间被声明。比如:一个JSP Document 包含来自标准JSTL核心库中的XHTML模板文字和JSP action, 它应该有一个<html>根元素,有以下的名称空间的声明:
<html
xmlns="http://www.w3c.org/1999/xhtml"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xml:lang="en" lang="en">
xmlns属性为XHTML名称空间设置默认的名称空间,xmlns:jsp属性把jsp前缀和定义为JSP标准action的元素关联起来,并且xmlns:c属性将前缀“c”和JSTL核心库定义的元素关联起来。
JSP Document已经是JSP规范中的一部分了,但是起初是作为一个可选择的特性,并且后来还有许多的局限性。JSP 2.0解决了大多数局限性的问题,使XML和JSP协同工作变得更加简单。
首先对于JSP 2.0来说,一个JSP Document 必须要有一个<jsp:root>根元素用以告诉容器它是哪一种类型的JSP页面。JSP 2.0通过这种新的方式来标识一个JSP Document文件解决了这个限制。如果以下条件中有一个为true,这个文件就将被JSP 2.0容器作为一个JSP Document来处理:
请求的路径与在web.xml中声明的URL匹配, JSP property group 声明有一个 <is-xml>元素设置为 true。关于 JSP property group 声明在上一篇中有详细的说明。
请求路径的扩展名是.jspx,除非这个扩展名匹配一个JSP property group声明的URL pattern,而JSP property group声明<is-xml>元素为false。 换句话说,.jspx是默认的JSP Document的扩展名,但是它可以被一个property group的声明置为无效。
请求路径扩展名是.jsp或者匹配一个JSP property group声明的URL pattern,而且这个文件中的root element是<jsp:root>。
这些新的规则使采用一个常规的XHTML文件(用JSP element处理动态内容)的形式来编写JSP Document成为可能,比如:不需要将所有的内容都放到<jsp:root> element中。如果类似下面的例子那样创建一个JSP property group,你甚至可以用扩展名为.html的文件:
...
<jsp-config>
<jsp-property-group>
<url-pattern>*.html</url-pattern>
<is-xml>true</is-xml>
</jsp-property-group>
</jsp-config>
...
用新的方法来生成XML Element
如果你曾经尝试用JSP1.2来编写JSP Document,那么你很可能在给XML element的attribute动态赋值时陷入了麻烦之中。比如,你想要给一个XML element的 class attribute赋值为一个用来保存用户风格样式参数的bean property。你首先可能试图像下面这样做:
<table class="%= user.getTableClass() %">
这样类型的Java 表达式,在一个JSP Document中能被用来作为一个 JSP action element的属性值。但是在template text中,JSP不会识别这样的语法,因此在这里是不会工作的。
用一个JSP action element来设置attribute的值,同样不可以:
<table class="<c:out value="${user.tableClass}" />">
这是因为在一个element attribute中一个格式完整的XML 文档不可以有一个“<”符号。
在JSP 1.2中,唯一动态设置一个element attribute 值的方法是使用令人生厌的CDATA 片断,将开始和结束的element当作原始文本(被动态生成的值包装)来处理:
<jsp:text><!CDATA[<table class="]]></jsp:text>
<c:out value="${user.tableClass}" />
<jsp:text><!CDATA[">]]></jsp:text>
JSP 2.0对于这种情况提供了两种简单的可选方案:1、在template text中使用一个EL表达式;2、使用一组新的标准的action element生成element。用EL表达式的例子如下:
<table class="${user.tableClass}">
一个JSP 2.0的容器对在template text中遇到的EL表达式求值,与在action 属性中一样。因此在大多数的情况下,这个解决方案是合适的。
如果你不能表示出你想要分配的值,你可以用三个新的标准action动态构造整个XML element,用JSP代码生成attribute的值。
<jsp:element name="table">
<jsp:attribute name="class">
<c:out value="${user.tableClass}" />
</jsp:attribute>
<jsp:body>
...
</jsp:body>
</jsp:element>
这个 <jsp:element> action 创建一个XML element,它的属性是由嵌套的<jsp:attribute> action 所创建的。这个属性的值被设置为<jsp:attribute>的运算结果,因此你可以使用自定义的action来产生它,比如:在这个例子中的<c:out> action。同样的element被设置为一个嵌套的<jsp:body>的运算结果。
一个用来声明XML的新的标准Action
一个XML document应该在文档的起始位置有一个XML的声明,可能跟随着一个DOCTYPE的声明。在JSP 2.0中,你可以用新增的<jsp:output>标准 action来生成这两个声明。
除非这个JSP Document有一个<jsp:root> element,就像它的root element(或者一个标签文件,标签文件我将在下一篇文章中讲述),JSP容器默认创建一个XML声明,就像这样:
<? xml version="1.0" encoding="encodingValue" ?>
encoding属性的值是字符编码,是由JSP页面的contentType属性决定的。如果你没有指定一个字符集,默认的就是UTF-8编码。如果你不希望生成一个XML的声明(因为这个JSP Document可能被包含在另外一个JSP页面中),你需要通过包含一个<jsp:output> action element来告诉JSP容器,就像这个JSP Document中那样:
<jsp:output omit-xml-declaration="true" />
用属性值true或者 yes来禁止生成这个声明。用false或者 no来允许生成这个声明。
一个DOCTYPE声明告诉一个XML parser(例如一个浏览器使用的parser)这个XML文档遵循了哪一个DTD(Document Type Declaration)。Parser可以用此信息来确认这个XML文档包含的仅是这个DTD声明的XML element。你不能在这个生成的JSP Documen中放置DOCTYPE声明(因为此时你正在表明这个 JSP Document和DTD一起编译),而是使用<jsp:output> action来告诉JSP容器为生成的结果添加一个声明:
<jsp:output doctype-root-element="html"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<jsp:directive.page contentType="text/html" />
就像这个例子一样,<jsp:output> action为XHTML给输出的结果添加了一个DOCTYPE声明:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
在这个例子中,我也包含了一个有contentType属性的<jsp:directive.page>声明,用来给输出的内容设置MIME类型为text/html,用来告诉浏览器如何对输出的内容进行处理。记住,因为XHTML特有的MIME类型实际是application/xhtml+xml,但是一些比较新的浏览器(特别是Internet Explorer 6)并不认识它,对于知道如何处理XHTML 1.0的大多数浏览器来说text/html才是一种允许接受的MIME类型。
结论
JSP2.0用XML document方式编写JSP页面更加容易,就像你已经在这部分看到的那样。最后一篇文章将要讲述和自定义标签库相关的新特性:新的标签文件格式和新的简单的标签API。
JSP 2.0: The New Deal, Part 4
byHans Bergsten, author of JavaServer Pages, 3rd Edition05/12/2004
在“JSP 2.0: 新特性”的最后一部分中,我们将要看两个新特性,它们使开发自定义标签库变得更加容易,它们是:标签文件和简化的tag-handler Java API。
以JSP Tag文件形式开发自定义Action
JSP试图让对Java没有经验的人写动态页面成为可能。在页面间,重用一部分动态的、复杂的内容到目前为止仍然是令人痛苦的事情。举例来说,你想要在一些页面中放置一个投票程序,包括问题和答案。在 JSP 2.0之前,你有三个选择:1、你可以拷贝并粘贴投票的程序到你希望添加的每一个页面中;2、一个稍好的选择是编写一个独立的JSP页面来生成投票的表单,并且在其他页面中用<jsp:include>或者 <c:import> action来包含这个独立的页面。但是你仅仅可以通过它来输入String类型的参数,不能是一个保存答案的bean或者一个Map。3、用一个Java tag handler类实现一个自定义action, 但是这需要建立在你对Java的了解基础上。
JSP 2.0增加了第四种选择:也就是用标签文件的形式开发自定义action。一个标签文件是一个纯文本文件,你对所有的动态生成的部分使用JSP element,就像在常规的JSP页面中那样。和一个Java 标签handler有同样目的,即:为自定义的action 提供逻辑运算。标签文件和JSP 页面之间主要的区别是一个标签文件有一个以.tag为扩展名的标签文件,用这个扩展名来区别jsp页面,并且你可以使用仅在标签文件中有效的一些新的标签来定义输入和输出。
更进一步来看,这里有一个标签文件“poll.tag”,它生成一个投票的表单:
<%@ tag body-content="empty" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true"
type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Question: ${question}<br>
<form action="result.jsp" target="result">
<input type="hidden" name="question" value="${question}">
<input type="hidden" name="votesMapName" value="${votesMapName}">
<input type="hidden" name="answersMapName" value="${answersMapName}">
<c:forEach items="${answers}" var="a">
<input type="radio" name="vote" value="${a.key}">${a.value}<br>
</c:forEach>
<input type="submit" value="Vote">
</form>
在文件的顶部,有一个标签指示符“tag”。这个标签指示符和你在JSP页面中使用的页面指示符“page”相似,它声明该文件的性质。这里,我使用了body-content属性来声明:在一个JSP页面中,一个表现这个标签文件的action element必须是empty,也就是这个action element不能有body。其他的值你可以使用“scriptless”(body可以包含除了脚本元素以外的任何期望的内容),或者“tagdependent”(容器传递主体内容给标签处理器(tag handler)而不做任何运算)。如果你曾经以Java class这种形式开发过自定义action,你可能就会从用来声明Java 标签处理器的TLD (Tag Library Descriptor)中认出“body-content”和“valid”。因为一个标签文件并不需要在一个TLD文件中被声明,标签指示符和其他特殊的标签文件指示符被用来提供相同类型的信息,TLD将这个信息提供给JSP容器。
在这个例子中跟随着这个标签指示符的是attribute指示符,用于同样的函数,和TLD 的element一样有着相同的名字:它声明有效的自定义action element 属性。这个自定义的用于投票的标签文件接受四个属性:
question: 投票的问题
answers:一个具有application-scope作用范围的Map,用于保存投票的答案,用数字做主键(key)
votesMapName:一个具有application-scope作用范围的变量名字,用于一个Map 保存每个答案的得票数,用答案数量做主键
answersMapName:一个具有application-scope作用范围的变量名字,用于保存答案的Map
每一个action element属性都有一个attribute指示符对应,它的名字用“name”属性来声明。在这个例子中,所有的action element属性都是需要的,就像对每一个attribute指示符都要用required属性来声明一样。“answers”属性值必须是一个“Map”,它被” type”属性来声明。所有其它的属性必须是String类型的,这是默认的类型,因此我没有特别为它们的属性指出类型。
一个taglib声明:在这个文件中使用了JSTL核心库,在一个用来输出question和每一个answer都有一个radio单选按钮的表单的EL表达式中,文件的主体使用了属性值(以page-scope变量形式,对标签文件有效)。answer 和 vote Map变量名字和question在表单中以隐藏域的形式在表单中出现,因此它们通过页面传递被处理,它们可以像下面这样应用:
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Poll Results</title>
</head>
<body bgcolor="white">
<c:set target="${applicationScope[param.votesMapName]}"
property="${param.vote}"
value="${applicationScope[param.votesMapName][param.vote] + 1}" />
<p>
Question: ${param.question}<br>
<c:forEach items="${applicationScope[param.answersMapName]}"
var="a">
${a.key}) ${a.value}:
${applicationScope[param.votesMapName][a.key]}<br>
</c:forEach>
</p>
</body>
</html>
这是一个常规的JSP页面,使用了JSTL action。在保存投票结果的Map中,它对符合键值条件的被选中的投票答案加一。通过votesMapName参数提供名字,在application作用范围内有效。接下来,在页面上输出question并且通过Map迭代输出answer,通过answersMapName参数提供名字在application作用范围内有效, 向页面输出每一个answer和当前answer的得票数。
比如何处理poll vote更令人感兴趣的是如何在JSP页面中使用由poll tag标签文件实现的自定义action,这里有一个例子:
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>
<html>
<head>
<title>My Page</title>
</head>
<body bgcolor="white">
<jsp:useBean id="myAnswers" scope="application"
class="java.util.TreeMap">
<c:set target="${myAnswers}" property="1" value="Yes" />
<c:set target="${myAnswers}" property="2" value="No" />
<c:set target="${myAnswers}" property="3" value="Maybe" />
</jsp:useBean>
<jsp:useBean id="myVotes" scope="application"
class="java.util.HashMap" />
...
<p>
<my:poll question="Will you start using tag files?"
answers="${myAnswers}"
answersMapName="myAnswers" votesMapName="myVotes" />
</p>
...
</body>
</html>
首先要注意的是“taglib”标签指示符,它声明使用的这个标签文件所在的标签库。要使用一个没有创建TLD的标签文件,你必须将这个标签文件保存在WEB-INF/tags目录下。例子中的poll.tag文件保存在WEB-INF/tags/mytags目录下,并且我让这个目录名字和“taglib”的“tagdir”属性的值相同。这样就告诉容器在这个目录下所找到的所有标签文件都属于同一个标签库,上面的例子中,在这个库中的action element被“taglib” 标签指示符的“prefix” 属性所指定的前缀所标识。另一种可选择的方式是:你可以将这些属于某个TLD的标签文件打包到一个jar文件中,用同样的“taglib” 标签指示符声明你使用了一个自定义标签库,并且用一个“uri”属性来替换“tagdir”属性。
在这个例子中,用于answer的“Map”对象和vote的计数由<jsp:useBean> action创建的,并且由JSTL的<c:set> action来操作,你当然可以用任何喜欢的方式来创建它们(比如:在一个context listener中,或者在Apache Struts application中用一个“plugin” class来创建)。无论它是如何被创建的,这个标签文件都是通过一个常规的JSP自定义action element被调用的。我使用一个EL表达式来对这个answers “Map”求值(“answers”的属性值)。标签文件默认是接受EL表达式的,你可以声明:一个静态值必须用“attribute”指示符的“rtexprvalue”属性来指定。
当你请求这个页面的时候,JSP容器就像通常一样对这个页面进行处理,通过element的“prefix” 和 “taglib“指示符的帮助来查找并定位 “<my:poll>”自定义标签的实现。容器可以按照它想要的任何方式来处理这个标签文件;Tomcat 5 容器把标签文件转换为一个Java标签处理器的类(tag handler class),编译并执行它。
处理自定义Action
就像用java语言编写的tag handler类一样,一个标签文件能要求容器处理自定义action element,更进一步地处理运算结果。
下面,在action element body中我们添加一个上一篇文章中poll question的小例子,就像下面这样:
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>
...
<html>
...
<body bgcolor="white">
...
<p>
<my:poll question="Will you start using tag files?"
answers="${myAnswers}"
answersMapName="myAnswers" votesMapName="myVotes" >
JSP 2.0 introduces a new way to develop custom action
tag handlers, called <i>tag files</i>
</my:poll>
</p>
...
</body>
</html>
这个例子仅仅包含文本,但是它也可以包含action element和EL 表达式。为了处理action element body并且讲结果添加到response中,我们需要象下面这样修改poll tag 文件:
<%@ tag body-content="scriptless" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true"
type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<p>
<jsp:doBody/>
</p>
Question: ${question}<br>
<form action="result.jsp" target="result">
<input type="hidden" name="question" value="${question}">
<input type="hidden" name="votesMapName" value="${votesMapName}">
<input type="hidden" name="answersMapName" value="${answersMapName}">
<c:forEach items="${answers}" var="a">
<input type="radio" name="vote" value="${a.key}">${a.value}<br>
</c:forEach>
<input type="submit" value="Vote">
</form>
首先,我们改变“tag”指示符的“body-content”属性值为“scriptless”。就像我前面说过的那样,这意味着在这个文件的主体内容中可以包含除了scripting element以外的任何内容。接下来,我们添加一个“<jsp:doBody>” action,它告诉容器去处理body,并且将处理结果添加到response中。你可以有选择性的使用“var” 属性,捕获处理的结果,并在下一步进行处理。
另外对于我已经讲述的新特性来说,一个标签文件可以通过变量,未声明的属性将信息返回给调用文件,并且可以有 “fragment” 属性(也就是说这些属性可以拥有action element和EL表达式,标签文件处理EL表达式采用的方式与处理action element类似)。在我这本书第11章中,你可以看到关于这些特性的全部内容,你可以在http://www.oreilly.com/catalog/jserverpages3/chapter/下载程序范例。
简单的Java Tag-Handler API
采用标签文件的方式编写自定义action tag handler,这是一个重大的新特性。尤其对自定义action来说,它往往要生成很多的HTML。但是在处理某些事情时,仅仅用JSP action 和 EL 表达式是非常难以做到的,因此仍然还是需要Java tag-handler API 。首先,对于JSP 2.0来说,用Java编写一个tag handler是非常复杂的。这是由于在容器和tag handler之间的交互需要处理action element body,而为了在action element body中支持Java scripting element就变得复杂起来!毕竟,如果在body中仅仅是包含template text、EL表达式、action element的话,一种相对简单的API就可以被设计出来。而这就恰恰是JSP 2.0所做的,并且它被恰当地命名为simple tag handler API。
这里有一个前面的范例poll的自定义action的例子,在前面我们是用一个标签文件来实现的:
package com.mycompany.mylib;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class PollTag extends SimpleTagSupport {
private String question;
private Map answers;
private String votesMapName;
private String answersMapName;
public void setQuestion(String question) {
this.question = question;
}
public void setAnswers(Map answers) {
this.answers = answers;
}
public void setVotesMapName(String votesMapName) {
this.votesMapName = votesMapName;
}
public void setAnswersMapName(String answersMapName) {
this.answersMapName = answersMapName;
}
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
JspFragment body = getJspBody();
if (body != null) {
out.println("<p>");
body.invoke(null);
out.println("</p>");
}
out.print("Question:");
out.print(question);
out.println("<br>");
out.println("<form action=/"result.jsp/" target=/"result/">");
out.print("<input type=/"hidden/" name=/"question/" value=/"");
out.print(question);
out.println("/">");
out.print("<input type=/"hidden/" name=/"votesMapName/" value=/"");
out.print(votesMapName);
out.println("/">");
out.print("<input type=/"hidden/" name=/"answersMapName/" value=/"");
out.print(answersMapName);
out.println("/">");
Iterator i = answers.keySet().iterator();
while (i.hasNext()) {
String key = (String) i.next();
String value = (String) answers.get(key);
out.print("<input type=/"radio/" name=/"vote/" value=/"");
out.print(key);
out.print("/">");
out.print(value);
out.println("<br>");
}
out.println("<input type=/"submit/" value=/"Vote/">");
out.println("</form>");
}
}
一个singple tag必须实现新增的javax.servlet.jsp.tagext.SimpleTag接口。这个例子中的tag handler继承javax.servlet.jsp.tagext.SimpleTagSupport类,父类提供了所有方法默认的实现。就像经典的tag handler类一样,你需要给每一个自定义的action属性创建setter方法,但是这里仅仅有一个处理方法,那就是要实现doTag()方法。
在tag handler这个例子中,方法doTag()试图通过调用getJspBody()方法(从SimpleTagSupport类继承而来)而获得一个可以执行的action element body(一个JspFragment实例)的请求。如果找到这个body, tag handler就用一个null 值来调用它,就像对invoke()方法的争论一样,这意味着处理的结果被添加到response输出中。至于<jsp:doBody> action,通过一个Writer实例来代替invoke()方法,你能捕获到处理的结果。此时doTag()方法输出带有radio单选按钮的HTML表单,就像标签文件的实现一样。
因为容器只能调用一个方法来让tag handler处理自己该做的事情,这代替了经典的tag handler的三个方法(doStartTag(), doAfterBody(), and doEndTag(),每一个方法的返回值告诉容器下一步该做什么),实现一个SimpleTag的tag handler比经典的tag-handler API更容易。另外,simple tag-handler的实例永远不能被再次使用,因此你不需要担心重置后的状态,而它的状态将会导致很多的问题。在我前面的文章中描述过这个问题,具体见"JSP 1.2: Great News for the JSP Community, Part 2"。
在一个TLD中声明一个simple tag handler,所用的方式与你声明一个经典的tag handler一样。但是除了JSP,<body-content> TLD element必须有一个值。这是因为simple tag handler不允许scripting element出现在自定义action的element body中。除此之外,使用一个实现了simple tag handler的自定义action与使用一个实现了经典tag handler的action没有任何区别。
simple tag handler 和 classic tag handler都可以通过实现一个新的接口(javax.faces.jsp.tagext.DynamicAttributes)来支持未声明的attribute。两种类型的attribute 都可以是JspFragment,包含其他的action或者EL表达式,tag handler可以用它进行任意多次的处理。在我的《JavaServer Pages, 3rd Edition》这本书中,你可以看到更多关于这些特性的内容。
小节
在以上系列文章中,我已经向你展示了JSP 2.0中所有的新特性:EL表达式,更好的错误处理机制,新的配置选项,改进的XML页面格式,用来开发tag handler的两个新途径。总之,这些改进之处使开发JSP页面变得易于理解和维护。
如果你想要尝试JSP2.0的新特性,我建议你使用Apache Tomcat 5。它是最早实现了JSP新规范的容器之一。在公布最终版本的JSP 2.0规范之前,一个被标记为“stable”版本的Tomcat是不能被发布的。但是最新的beta版已经被证实是非常稳定的,不要理会beta版的标记。Tomcat 5在the Jakarta Project site可以下载。