首次被JSTL 1.0引入的EL现在被合并到JSP规范中,就像应用template text一样地使用所有的标准的和定制的组件。
新的EL已经被扩展,具备一个函数调用机制,JSTL1.1整合了一系列经常需要使用的函数。
新增加的变量和servlet 规范定义的错误处理机制被更好地组织起来。通过新增加的变量,JSP error pages 现在可以提供更多的错误信息。
容器因为更加严格的语法检查可以更容易地找出发生的错误。
所有的J2EE 1.4规范(包括JSP 2.0 和 Servlet 2.4),为了声明部署的规则描述而应用了XML schema。这样的好处之一是你现在可以通过任何顺序列出web.xml文件中的描述。JSP 2.0也增加了一些新的配置选项用于部署描述,允许通过全局的配置来代替基于每页的配置。
由于更具伸缩性的规则和新的自定义action element,现在就像编写XML文件一样,编写JSP页面变得更加容易。
定制的标签库现在可以开发成一系列的标签文件(具有JSP元素的文本文件),标签处理器可以使用新的、简化的标签处理器的API。与此同时,新规范加入了一些新的特性,比如:支持在jsp页面上显示动态属性列表和可执行片断属性。
在众多的书籍中,这是头一个讲解JSP 2.0新特性的文章。在这一部分,我们将看到和EL相关的信息,其他的新特性留到后面。在这里我假定读者已经熟悉JSP 1.2,而且至少听说过JSTL。
你可能对这本第三版的《JavaServer Pages》感兴趣。这本书中,我尽可能在细节上讲述所有的内容,而且并不认为你对JSP或者JSTL了解一切。这本书预计在2003年12月 出版,但是你现在可以在http://www.amazon.com、Barnes&Noble,或者其他在线书店预订。
EL(The Expression Language)
如果过去使用过JSTL,那么你可能已经熟悉了EL。EL在JSTL 1.0规范中被引入,用来在运行期间对Java表达式中action element属性赋值提供另一种选择。当JSTL EL已经非常迅速的流行起来情况下,还是存在一个问题: JSTL EL 表达式仅仅可以与JSTL和custom action一起使用,怎样才能使用非标准API对EL表达式求值?
JSP 2.0中,JSP容器自己可以理解EL表达式。这使你在所有过去只能应用Java表达式的地方应用EL表达式成为可能,比如:标准和定制action的属性值,模板文本。
在我们看具体的例子前,让我们更进一步的看看什么是EL。EL是从JavaScript中获得启发的一种语言,XPath(一种用来访问XML文档的语言),但是EL在对变量的null值和执行更多数据类型的自动类型转换的处理上更加宽松。这些新特性对于web应用非常重要,在这些应用中输入通常通过html表单的request parameter来得到。这些参数可能仅仅在某些请求下才能体现出来,而且浏览器经常将request parameter作为文本发送,然而应用程序经常需要把他们作为数字类型、布尔类型(true 或者 false)来使用。通过EL,你根本就很少需要关心缺少某些参数的值或者类型转换。
一个EL表达式包含变量和操作符。任何存储在某个JSP作用范围(如:page、 request、session、application)的bean能被作为一个EL变量来使用。另外,EL支持以下预定义的变量:
变量名称
说明
pageScope
一个包含所有page scope范围的变量集合 (a java.util.Map)
requestScope
一个包含所有request scope范围的变量集合 (a java.util.Map)
sessionScope
一个包含所有session scope范围的变量集合 (a java.util.Map)
applicationScope
一个包含所有application scope范围的变量集合 (a java.util.Map)
param
一个包含所有请求参数的集合 (a java.util.Map),通过每个参数对应一个String值的方式赋值
paramValues
一个包含所有请求参数的集合 (a java.util.Map),通过每个参数对应一个String数组的方式赋值
header
一个包含所有请求的头信息的集合, (a java.util.Map) ,通过每个头信息对应一个String值的方式赋值
headerValues
一个包含所有请求的头信息的集合 (a java.util.Map) ,通过每个头信息的值都保存在一个String数组的方式赋值
cookie
一个包含所有请求的 cookie集合 (a java.util.Map), 通过每一个cookie(javax.servlet.http.Cookie)对应一个cookie值的方式赋值
initParam
一个包含所有应用程序初始化参数的集合(a java.util.Map) ,通过每个参数分别对应一个String值的方式赋值
pageContext
一个javax.servlet.jsp.PageContext类的实例, 用来提供访问不同的请求数据
操作符描述了你对变量所期望的操作。如果你之前曾经使用过任何编程语言的话,在EL表达式中所使用的操作符对你来说可能看起来很熟悉。因为它们和那些在大多数语言中所支持的操作符一样。
Operator
Description
.
访问一个bean属性或者 Map entry
[]
访问一个数组或者链表元素
()
对子表达式分组,用来改变赋值顺序
? :
条件语句,比如: 条件 ? ifTrue : ifFalse.如果条件为真,表达式值为前者,反之为后者
+
数学运算符,加操作
-
数学运算符,减操作或者对一个值取反
*
数学运算符,乘操作
/ or div
数学运算符,除操作
% or mod
数学运算符,模操作(取余)
== or eq
逻辑运算符,判断符号左右两端是否相等,如果相等返回true,否则返回false
!= or ne
逻辑运算符,判断符号左右两端是否不相等,如果不相等返回true,否则返回false
< or lt
逻辑运算符,判断符号左边是否小于右边,如果小于返回true,否则返回false
> or gt
逻辑运算符,判断符号左边是否大于右边,如果大于返回true,否则返回false
<= or le
逻辑运算符,判断符号左边是否小于或者等于右边,如果小于或者等于返回true,否则返回false
>= or ge
逻辑运算符,判断符号左边是否大于或者等于右边,如果大于或者等于返回true,否则返回false
&& or and
逻辑运算符,与操作赋。如果左右两边同为true返回true,否则返回false
|| or or
逻辑运算符,或操作赋。如果左右两边有任何一边为true返回true,否则返回false
! or not
逻辑运算符,非操作赋。如果对true取运算返回false,否则返回true
empty
用来对一个空变量值进行判断: null、一个空String、空数组、 空Map、没有条目的Collection集合
func(args)
调用方法, func是方法名,args是参数,可以没有,或者有一个、多个参数.参数间用逗号隔开
一个EL表达式可以包含:数字、文本(在单引号或者双引号之间)、布尔值、null值。
因为一个EL表达式可以出现在静态文本出现的地方,因此你必须告诉JSP容器它应该被当作一个EL表达式来处理。你可以通过使用定界符来做到这一点。一个EL表达式总是以”${ }”来标记(一个“$”符号和一个左花括号,右花括号)。这里有一个EL表达式,它将一个命名为amount的变量加5:
${amount + 5}
如果你想要将5加到一个bean的property上,可以使用property访问操作符:
${order.amount + 5}
在当前这个指定的bean或者collection集合中,Property访问操作符(一个“.“符号)告诉EL去寻找名字为amount的property。
${order['amount'] + 5}
在[]之间的值必须是一个property的名字(就像上面的例子中那样)或者是一个保存property名字的变量(或者是一个完整的EL子表达式)。
EL表达式可以被用来赋值给任何标准的或者定制的JSP行为属性(action attribute),这些行为属性被标记为可以接受动态值(或者请求期间的属性值,就象它被正式调用一样):
<c:out value="${order.amount + 5}"/>
在JSP 2.0之前,你不得不使用Java表达式去给一个属性动态赋值。在过去的很多年中,这已经成为语法混乱的一个普遍根源。
最后,EL表达式可以在页面中和模板直接混合使用。当你生成HTML并且需要设置一个动态值给一个属性的时候,这非常方便:
<input name="firstName" value="${customer.firstName}">
JSP 1.2中,你不得不使用JSTL的<c:out>来实现同样的事情,最后把各种不同类型的元素混合起来,这导致程序理解起来非常的困难:
<input name="firstName"
value="<c:out value="${customer.firstName}"/>" >
新JSTL 1.1 Tag Library 标识符
JSTL1.1发布的是一个初级的版本,主要目的是用来整合JSTL和JSP2.0 。最明显的变化是JSTL1.0 “孪生函数库”(一组库用来接受EL表达式,另外一组用来接受JAVA表达式),而它们已经被一组既可以用于EL表达式也可以用于JAVA表达式的函数库所代替。
在JSTL 1.1中使用以下标识符:
库
URI
前缀
Core
http://java.sun.com/jsp/jstl/core
c
XML processing
http://java.sun.com/jsp/jstl/xml
x
I18N formatting
http://java.sun.com/jsp/jstl/fmt
fmt
Database access
http://java.sun.com/jsp/jstl/sql
sql
Functions
http://java.sun.com/jsp/jstl/functions
fn
如果你曾经使用过JSTL1.0,你可能会注意到新的标识符和旧的EL库标试符一模一样,除了加入了“/jsp path” element。你也可能注意到在JSTL1.1中有一个库,包含了EL的函数。我们稍后就会看到。
一个新的EL操作符
在JSP页面中一个非常普遍的需求就是:当某个条件为真时,要在网页中包含一些文字。在JSP1.2和JSTL1.1中,用具有代表性的<c:if>来实现,但是这样做非常繁琐。JSP2.0增加了一个新的条件操作符用于EL,以更加优雅的方式来处理这样的情况。这个条件操作符存在于很多编程语言中(比如:Java,C,JavaScript),因此你可能以前就见过它。它判断一个布尔的条件,当条件为真或者假时,分别取不同的结果。
一个能清楚说明它如何工作的例子:
- <select name="artist">
- <option value="1" ${param.artist == 1 ? 'selected' : ''}>
- Vesica Pisces
- <option value="2" ${param.artist == 2 ? 'selected' : ''}>
- Cortical Control
- <option value="3" ${param.artist == 3 ? 'selected' : ''}>
- Vida Vierra
- </select>
在这里,我使用了EL表达式和条件操作符来选择是否包含 html 中的 “selected”属性,只有符合条件的 “option” 才被添加 “selected” 属性。如果条件(param.artist==1)为真时,前面的“selected” 才被添加到网页中;否则就添加后面的(在这里是空字符串 ‘’)到页面中。
EL函数
当EL从JSTL规范中移到JSP规范中,它使用了一个如何进行函数调用的技巧。这个EL函数语法非常简单:方法名,紧接着在圆括号中有一组参数:
<%@ taglib prefix="fn"
uri="http://java.sun.com/jsp/jstl/functions" %>
${fn:length(myCollection)}
这是一个属于标签库中的函数,并且函数名字在页面中所包含的前缀要指定taglib库。在这个例子中,我使用了前缀fn,这是JSTL function库默认的前缀。
标签库描述符(Tag Library Descriptor,TLD)将函数名称映射到一个由JAVA实现的静态方法中:
- <function>
- <description>
- Returns the number of items in a collection or the number of characters in a string.
- </description>
- <name>length</name>
- <function-class>
- org.apache.taglibs.standard.functions.Functions
- </function-class>
- <function-signature>
- int length(java.lang.Object)
- </function-signature>
- </function>
在这里最有趣的element是<function-signature>。它包含一个函数返回类型的声明,静态的方法的名字,在圆括号中声明该方法所有参数的类型(可以没有参数或者有多个,参数间用逗号间隔开)。返回值类型和参数类型必须是java的原始类型(Object)或者是其他合法类型。
这个静态方法 length()在Jakarta Taglibs标准库中用类似于下面的代码实现的:
- public static int length(Object obj)
- throws JspTagException {
- if (obj == null)
- return 0;
- if (obj instanceof String)
- return ((String)obj).length();
- if (obj instanceof Collection)
- return ((Collection)obj).size();
- if (obj instanceof Map)
- return ((Map)obj).size();
- int count = 0;
- if (obj instanceof Iterator) {
- Iterator iter = (Iterator) obj;
- count = 0;
- while (iter.hasNext()) {
- count++;
- iter.next();
- }
- return count;
- }
- if (obj instanceof Enumeration) {
- Enumeration enum = (Enumeration) obj;
- count = 0;
- while (enum.hasMoreElements()) {
- count++;
- enum.nextElement();
- }
- return count;
- }
- try {
- count = Array.getLength(obj);
- return count;
- } catch (IllegalArgumentException ex) {}
- throw new JspTagException("Unsupported type"));
- }
就像你所看到的,在那里没有什么出奇的地方。它是一个常规的静态方法,这个函数中通过对运行期中的参数类别的判断,找出参数的长度。
除了在这个方法中使用的length()方法,JSTL1.1标签库还包含了许多其它经常使用的函数:
函数
描述
fn:contains(string, substring)
如果参数string中包含参数substring,返回true
fn:containsIgnoreCase(string, substring)
如果参数string中包含参数substring(忽略大小写),返回true
fn:endsWith(string, suffix)
如果参数 string 以参数suffix结尾,返回true
fn:escapeXml(string)
将有特殊意义的XML (和HTML)转换为对应的XML character entity code,并返回
fn:indexOf(string, substring)
返回参数substring在参数string中第一次出现的位置
fn:join(array, separator)
将一个给定的数组array用给定的间隔符separator串在一起,组成一个新的字符串并返回。
fn:length(item)
返回参数item中包含元素的数量。参数Item类型是数组、collection或者String。如果是String类型,返回值是String中的字符数。
fn:replace(string, before, after)
返回一个String对象。用参数after字符串替换参数string中所有出现参数before字符串的地方,并返回替换后的结果
fn:split(string, separator)
返回一个数组,以参数separator 为分割符分割参数string,分割后的每一部分就是数组的一个元素
fn:startsWith(string, prefix)
如果参数string以参数prefix开头,返回true
fn:substring(string, begin, end)
返回参数string部分字符串, 从参数begin开始到参数end位置,包括end位置的字符
fn:substringAfter(string, substring)
返回参数substring在参数string中后面的那一部分字符串
fn:substringBefore(string, substring)
返回参数substring在参数string中前面的那一部分字符串
fn:toLowerCase(string)
将参数string所有的字符变为小写,并将其返回
fn:toUpperCase(string)
将参数string所有的字符变为大写,并将其返回
fn:trim(string)
去除参数string 首尾的空格,并将其返回
结束语:
在这篇文章中,我从EL讲到JSTL1.1规范、EL新特色和JSTL 1.1函数库。接下来的部分我将要告诉你:关于JSP error-page的改进和增强; jsp:id 属性带来的益处;新的配置属性描述符;JSP2.0如何使JSP操作XML变得更加容易;自定义标签库的新特性。
JSP 2.0: The New Deal, Part 2
by Hans Bergsten, author of JavaServer Pages, 3rd Edition,12/03/2003
这篇文章是讲述加入到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>
你可以在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 语法错误的报告
在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>
- ...
总地来说同<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">
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>
- ...
如果你曾经尝试用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>
一个用来声明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>
在这个例子中跟随着这个标签指示符的是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>
比如何处理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>
在这个例子中,用于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>
- <%@ 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可以下载。