作者: Hans Bergsten , 《JavaServer Pages, 3rd Edition 》11/05/2003
期待已久的日子即将到来: 最新版JavaServer Pages (JSP) 2.0 规范即将和其他的J2EE 1.4一同发布。新的JSP版本有一个新的飞跃,采用了新的方式:由于新的语言表达式(Expression Language,以下简称为EL)和JSP标准标签库(JSP Standard Tag Library ,以下简称为JSTL)这两种新的方式,在页面中不需要用java,对于开发一般的应用来说,重用代码变得更加容易。更具体来说,JSP 2.0带来了以下的优点:
- 首次被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变得更加容易;自定义标签库的新特性。
如 果你想要尝试JSP2.0的新特性,我建议你使用Apache Tomcat 5。它是最早实现了JSP新规范的容器之一。在公布最终版本的JSP 2.0规范之前,一个被标记为“stable”版本的Tomcat是不能被发布的。但是最新的beta版已经被证实是非常稳定的,不要理会beta版的标 记。Tomcat 5在the Jakarta Project site 可以下载。
Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition .
Return to ONJava.com .