JSTL
入门
:
表达式语言
通过避免使用脚本编制元素来简化对
JSP
应用程序的软件维护
Core
库
(
核心库
)
URI:
http://java.sun.com/jsp/jstl/core
前缀:
c
1.
<c:catch>
<c:catch>动作用于捕获 JSP 元素在其体中抛出的异常,从而提供细粒度的错误
控制,此异常也可以保存为一个页面作用域变量。
语法:
<c:catch [var="var"]>
JSP elements
</c:catch>
属性:
属性名
Java 类型
是否接受动态值
描述
var
String
No
变量名
示例:
<c:catch var="importException">
<fmt:parseDate value="${param.empDate}" dateStyle="short" />
</c:catch>
<c:if test="${importException != null}">
<jsp:forward page="input.jsp">
<jsp:param name="msg" value="Invalid date format" />
</jsp:forward>
</c:if>
2.
<c:choose>
<c:choose>动作用于控制嵌套<c:when>和<c:otherwise>动作的处理,它只允许
第一个测试表达式计算为 true 的<c:when>动作得到处理;如果所有<c:when>动
作的测试表达式都计算为 false,则会处理一个<c:otherwise>动作。
语法:
<c:catch [var="var"]>
JSP elements
</c:catch>
属性:
无
示例:
<c:choose>
<c:when test="${product.onSale}">
${product.salesPrice} On sale!
</c:when>
<c:otherwise>
${product.price}
</c:otherwise>
</c:choose>
3.
<c:forEach>
<c:forEach>动作将其体反复计算固定次数,或者针对一个集合中的每个元素分
别计算一次体。当前元素(如果未指定集合则为当前索引)和迭代状态可以通过
嵌套变量提供给体中的动作元素。
此动作接受属性表中所列的类型集合。当前元素的类型即为底层集合的类型,仅
有两个例外。对于一个基本类型的数组,当前元素将作为相应包装类(Integer、
Float 等等)的一个实例提供。对于一个 java.util.Map,当前元素则作为一个
java.util.Map.Entry 提供。
语法 1:基于集合元素进行迭代
<c:forEach items="collection" [var="var"] [varStatus="varStatus"]
[begin="startIndex"] [end="stopIndex"] [step="increment"]>
JSP elements
</c:forEach>
语法 2:迭代固定次数
<c:forEach [var="var"] [varStatus="varStatus"]
begin="startIndex" end="stopIndex" [step="increment"]>
JSP elements
</c:forEach>
属性
属性名
Java 类型
是否
接受
动态
值
描述
begin
int
Yes
结合集合使用时的开始索引,从
0 计起。对于集合来说默认为 0
end
int
Yes
结合集合使用时的结束索引(元
素引要小于等于此结束索引),
从 0 计起。默认为集合的最后一
个元素。如果 end 小于 begin,
则根本不计算体集合,迭代即要
针对此集合进行。
items
java.util.Collection
,
java.util.Iterator
,
java.util.Enumeration
,
java.util.Map
, 对象数组或
基本类型数组。
Yes
集合,迭代即要针对此集合进
行。
step
int
Yes
每次迭代时索引的递增值。默认
为 1。
var
String
No
保存当前元素的嵌套变量的名
字。
varStatus String
No
保存 LoopTagStatus 对象的嵌
套变量的名字。
示例:
<%-- Iterate five times, writing 1, 2, 3, 4, 5 --%>
<c:forEach begin="1" end="5" var="current">
${current}
</c:forEach>
<%-- Iterate over all request parameters --%>
<c:forEach items="${param}" var="current">
Name: <c:out value="${current.key}" />
Value: <c:out value="${current.value}" />
</c:forEach>
--Update:06/10/27
4.
<c:forTokens>
<c:forTokens>动作对于 String 中的每个 token(单字)计算其体一次,在 String
中这些 token 由某个所指定的分隔符相分隔。当前 token 和迭代状态可以通过嵌
套变量提供给体中的动作元素。(拆分字符串)
语法
<c:forTokens items="
stringOfTokens
" delims="
delimiters
"
[var="
var
"] [varStatus="
varStatus
"]
[begin="
startIndex
"] [end="
stopIndex
"] [step="
increment
"]>
JSP elements
</c:forTokens>
属性
属性名
Java 类
型
是否接
受动态
值
描述
items
String
Yes
一组 token,迭代即要针对这些 token 进行
delims
String
Yes
分隔符列表
var
String
No
保存当前元素的嵌套变量的名字
varStatus String
No
保存
LoopTagStatus
对象的嵌套变量的名字
begin
int
Yes
开始索引,从 0 计起,默认为 0
end
int
Yes
结束索引(小于等于此索引),从 0 计起。默认
为最后一个 token。如果 end 小于 begin,则根本
不计算体。
step
int
Yes
每次迭代时索引的递增值。默认为 1。
示例
<%-- Iterate over tokens separated by vertical bars --%>
<c:forTokens items="${tokens}" delims="|" var="current">
<c:out value="${current }" />
</c:forTokens>
5.
<c:if>
<c:if>动作仅当所指定的表达式计算为 true 时才计算其体。计算结果也可以保
存为一个作用域 Boolean 变量。
语法 1:没有体
<c:if test="
booleanExpression
"
var="
var
" [scope="
page
|request|session|application"]/>
语法 2:有体
<c:if test="
booleanExpression
">
JSP elements
</c:if>
属性
属性名
Java 类型
是否接受动态值
描述
test
boolean
Yes
测试表达式
var
String
No
变量名
scope
String
No
变量作用域
示例
<c:if test="${empty param.empDate}">
<jsp:forward page="input.jsp">
<jsp:param name="msg" value="Missing the Employment Date" />
</jsp:forward>
</c:if>
2003
年
5
月
27
日
JSP
标准标记库(
JSP Standard Tag Library
,
JSTL
)是一个实现
Web
应用程序中常见的通用功能的
定制标记库集,这些功能包括迭代和条件判断、数据管理格式化、
XML
操作以及数据库访问。在
developerworks
上其新系列的第一篇文章中,软件工程师
Mark Kolb
向您展示了如何使用
JSTL
标
记来避免在
JSP
页面中使用脚本编制元素。您还将了解如何通过从表示层删除源代码来简化软件维护。
最后,您将了解
JSTL
经过简化的表达式语言,它允许在不必使用功能齐全的编程语言的情况下对
JSTL
操作指定动态属性值。
JavaServer Pages
(
JSP
)是用于
J2EE
平台的标准表示层技术。
JSP
技术提供了用于执行计算(这些
计算用来动态地生成页面内容)的脚本编制元素和操作。脚本编制元素允许在
JSP
页面中包括程序源代
码,在为响应用户请求而呈现页面时可以执行这些源代码。操作将计算操作封装到很象
HTML
或
XML
标
记的标记中,
JSP
页面的模板文本通常包含这些标记。
JSP
规范只将几种操作定义成了标准,但从
JSP 1.1
开始,开发人员已经能够以定制标记库的方式创建其自己的操作了。
JSP
标准标记库(
JSTL
)是
JSP 1.2
定制标记库集,这些标记库实现大量服务器端
Java
应用程序常用
的基本功能。通过为典型表示层任务(如数据格式化和迭代或条件内容)提供标准实现,
JSTL
使
JSP
作
者可以专注于特定于应用程序的开发需求,而不是为这些通用操作
“
另起炉灶
”
。
当然,您可以使用
JSP
脚本编制元素(
scriptlet
、表达式和声明)来实现此类任务。例如,可以使用三
个
scriptlet
实现条件内容,清单
1
中着重显示了这三个
scriptlet
。但是,因为脚本编制元素依赖于在
页面中嵌入程序源代码(通常是
Java
代码),所以对于使用这些脚本编制元素的
JSP
页面,其软件维
护任务的复杂度大大增加了。例如,清单
1
中的
scriptlet
示例严格地依赖于花括号的正确匹配。如果
不经意间引入了一个语法错误,则条件内容中的嵌套其它
scriptlet
可能会造成严重破坏,并且在
JSP
容
器编译该页面时,要使所产生的错误信息有意义可能会很困难。
清单
1.
通过
scriptlet
实现条件内容
<% if (user.getRole() == "member")) { %>
<p>Welcome, member!</p>
<% } else { %>
<p>Welcome, guest!</p>
<% } %>
修正此类问题通常需要相当丰富的编程经验。尽管通常会由十分精通页面布局和图形设计的设计人员来开
发和维护
JSP
,但是同一页面中的脚本编制元素出现问题时,需要程序员的介入。这种状况将单个文件中
代码的责任分担给多人,因而使得开发、调试和增强此类
JSP
页面成为很麻烦的任务。通过将常用功能
包装到定制标记库的标准集合中,
JSTL
使
JSP
作者可以减少对编制脚本元素的需求,甚至可以不需要它
们,并避免了相关的维护成本。
JSTL 1.0
JSTL 1.0
发布于
2002
年
6
月,由四个定制标记库(
core
、
format
、
xml
和
sql
)和一对通用标记库
验证器(
ScriptFreeTLV
和
PermittedTaglibsTLV
)组成。
core
标记库提供了定制操作,通过限制了
作用域的变量管理数据,以及执行页面内容的迭代和条件操作。它还提供了用来生成和操作
URL
的标记。
顾名思义,
format
标记库定义了用来格式化数据(尤其是数字和日期)的操作。它还支持使用本地化资
源束进行
JSP
页面的国际化。
xml
库包含一些标记,这些标记用来操作通过
XML
表示的数据,而
sql
库
定义了用来查询关系数据库的操作。
两个
JSTL
标记库验证器允许开发人员在其
JSP
应用程序中强制使用编码标准。可以配置
ScriptFreeTLV
验证器以在
JSP
页面中禁用各种类型的
JSP
脚本元素
― scriptlet
、表达式和声明。
类似地,
PermittedTaglibsTLV
验证器可以用来限制可能由应用程序的
JSP
页面访问的定制标记库集
(包括
JSTL
标记库)。
尽管
JSTL
最终将会成为
J2EE
平台的必需组件,但目前只有少数应用程序服务器包括它。
JSTL 1.0
的
参考实现可作为
Apache
软件基金会(
Apache Software Foundation
)的
Jakarta Taglibs
项目(请
参阅
参考资料
)的一部分而获得。可以将该参考实现中的定制标记库合并到任何支持
JSP 1.2
和
Servlet
2.3
规范的服务器,以添加对
JSTL
的支持。
表达式语言
在
JSP 1.2
中,可以使用静态字符串或表达式(如果允许的话)指定
JSP
操作的属性。例如,在清单
2
中,对
<jsp:setProperty>
操作的
name
和
property
属性指定了静态值,而用表达式指定了其
value
属性。这个操作的效果是将请求参数的当前值赋予命名的
bean
特性。以这种形式使用的表达式被称为
请
求时属性值(
request-time attribute value
)
,这是构建到
JSP
规范中的用于动态指定属性值的唯一
机制。
清单
2.
合并请求时属性值的
JSP
操作
<jsp:setProperty name="user" property="timezonePref"
value='<%= request.getParameter("timezone") %>'/>
因为请求时属性值是用表达式指定的,所以它们往往有和其它脚本元素一样的软件维护问题。因此,
JSTL
定制标记支持另一种用于指定动态属性值的机制。可以用简化的
表达式语言
(
EL
)而不使用完整的
JSP
表
达式来指定
JSTL
操作的属性值。
EL
提供了一些标识符、存取器和运算符,用来检索和操作驻留在
JSP
容器中的数据。
EL
在某种程度上以
EcmaScript
(请参阅
参考资料
)和
XML
路径语言(
XML Path
Language
,
XPath
)为基础,因此页面设计人员和程序员都应该熟悉它的语法。
EL
擅长寻找对象及其特
性,然后对它们执行简单操作;它不是编程语言,甚至不是脚本编制语言。但是,与
JSTL
标记一起使用
时,它就能使用简单而又方便的符号来表示复杂的行为。
EL
表达式的格式是这样的:用美元符号(
$
)定
界,内容包括在花括号(
{}
)中,如清单
3
所示。
清单
3.
说明
EL
表达式定界符的
JSTL
操作
<c:out value="${user.firstName}"/>
此外,您可以将多个表达式与静态文本组合在一起以通过字符串并置来构造动态属性值,如清单
4
所示。
单独的表达式由标识符、存取器、文字和运算符组成。标识符用来引用存储在数据中心中的数据对象。
EL
有
11
个保留标识符,对应于
11
个
EL
隐式对象。假定所有其它标识符都引用
限制了作用域的变量
。
存取器用来检索对象的特性或集合的元素。文字表示固定的值
―
数字、字符、字符串、布尔型或空值。
运算符允许对数据和文字进行组合以及比较。
清单
4.
组合静态文本和多个
EL
表达式以指定动态属性值
<c:out value="Hello ${user.firstName} ${user.lastName}"/>
限制了作用域的变量
JSP API
通过
<jsp:useBean>
操作允许从
JSP
容器内的四个不同作用域中存储和检索数据。
JSTL
通
过提供用于指定和除去这些作用域中的对象的附加操作来扩展这一能力。此外,
EL
提供将这些对象作为
限制了作用域的变量进行检索的内置支持。特别地,任何出现在
EL
表达式中但不对应于任何
EL
隐式对
象的标识符,都被自动假定为引用存储在四个
JSP
作用域的其中某个中的对象,这四个作用域是:
页面作用域
请求作用域
会话作用域
应用程序作用域
您可能还记得,只有在为特定请求处理页面期间才能检索存储在该页面作用域中的对象。如果对象是存储
在请求作用域中的,可以在处理所有参与处理某请求的页面期间检索这些对象(譬如在对某个请求的处理
中遇到了一个或多个
<jsp:include>
或
<jsp:forward>
操作)。如果对象是存储在会话作用域中的,
则在与
Web
应用程序的交互式会话期间,可以由用户访问的任何页面检索它(即,直到与该用户交互相
关联的
HttpSession
对象无效为止)。可以由任何用户从任何页面访问存储在应用程序作用域中的对象,
直到卸载
Web
应用程序本身为止(通常是由于关闭
JSP
容器所致)。
通过将字符串映射为期望作用域中的对象来将对象存储到该作用域。然后,就可以通过提供相同字符串来
从该作用域检索该对象。在作用域的映射中查找字符串,并返回被映射的对象。在
Servlet API
中,将此
类对象称为相应作用域的
属性
。但是,在
EL
的上下文中,也将与属性相关联的字符串看作变量的名称,
该变量通过属性映射的方式获得特定的值。
在
EL
中,与隐式对象无关联的标识符被认为是存储在四个
JSP
作用域中的名称对象。首先对页面作用
域检查是否存在这样的标识符,其次对请求作用域、然后对会话作用域、最后对应用程序作用域依次进行
这样的检查,然后测试该标识符的名称是否与存储在该作用域中的某个对象的名称匹配。第一个这样的匹
配作为
EL
标识符的值被返回。通过这种方法,可以将
EL
标识符看作引用限制了作用域的变量。
从更技术的方面来说,没有映射到隐式对象的标识符是用
PageContext
实例的
findAttribute()
方法
求值的,该实例表示对页面的处理,在该页面上,当前正在处理用于请求的表达式。标识符的名称作为参
数传递给这个方法,然后该方法依次在四个作用域中搜索具有相同名称的属性。并将所找到的第一个匹配
项作为
findAttribute()
方法的值返回。如果未在这四个作用域中找到这样的属性,则返回
null
。
最终,限制了作用域的变量是四个
JSP
作用域的属性,这些属性具有可以用作
EL
标识符的名称。只要
对限制了作用域的变量赋予由字母数字组成的名称,就可以通过
JSP
中提供的用于设置属性的任何机制
来创建它们。这包括内置的
<jsp:useBean>
操作,以及由
Servlet API
中的几个类定义的
setAttribute()
方法。此外,四个
JSTL
库中定义的许多定制标记本身就能够设置作为限制了作用域的
变量使用的属性值。
隐式对象
表
1
中列出了
11
个
EL
隐式对象的标识符。不要将这些对象与
JSP
隐式对象(一共只有九个)混淆,
其中只有一个对象是它们所共有的。
表
1. EL
隐式对象
类别
标识符
描述
JSP
pageContext
PageContext
实例对应于当前页面的处理
作用域
pageScope
与页面作用域属性的名称和值相关联的
Map
类
requestScope
与请求作用域属性的名称和值相关联的
Map
类
sessionScope
与会话作用域属性的名称和值相关联的
Map
类
applicationScope
与应用程序作用域属性的名称和值相关联的
Map
类
请求参数
param
按名称存储请求参数的主要值的
Map
类
paramValues
将请求参数的所有值作为
String
数组存储的
Map
类
请求头
header
按名称存储请求头主要值的
Map
类
headerValues
将请求头的所有值作为
String
数组存储的
Map
类
Cookie
cookie
按名称存储请求附带的
cookie
的
Map
类
初始化参数
initParam
按名称存储
Web
应用程序上下文初始化参数的
Map
类
尽管
JSP
和
EL
隐式对象中只有一个公共对象(
pageContext
),但通过
EL
也可以访问其它
JSP
隐
式对象。原因是
pageContext
拥有访问所有其它八个
JSP
隐式对象的特性。实际上,这是将它包括在
EL
隐式对象中的主要理由。
其余所有
EL
隐式对象都是映射,可以用来查找对应于名称的对象。前四个映射表示先前讨论的各种属性
作用域。可以用它们来查找特定作用域中的标识符,而不用依赖于
EL
在缺省情况下使用的顺序查找过程。
接下来的四个映射用来获取请求参数和请求头的值。因为
HTTP
协议允许请求参数和请求头具有多个值,
所以它们各有一对映射。每对中的第一个映射返回请求参数或头的主要值,通常是恰巧在实际请求中首先
指定的那个值。每对中第二个映射允许检索参数或头的所有值。这些映射中的键是参数或头的名称,但这
些值是
String
对象的数组,其中的每个元素都是单一参数值或头值。
cookie
隐式对象提供了对由请求设置的
cookie
名称的访问。这个对象将所有与请求相关联的
cookie
名称映射到表示那些
cookie
特性的
Cookie
对象。
最后一个
EL
隐式对象
initParam
是一个映射,它储存与
Web
应用程序相关联的所有上下文的初始化
参数的名称和值。初始化参数是通过
web.xml
部署描述符文件指定的,该文件位于应用程序的
WEB-INF
目录中。
存取器
因为
EL
标识符是作为隐式对象或限制了作用域的变量(通过属性来实现)解析的,因此有必要将它们转
换成
Java
对象。
EL
可以自动包装和解包其相应的
Java
类中的基本类型(例如,可以在后台将
int
强
制转换成
Integer
类,反之亦可),但大多数的标识符将成为指向完整的
Java
对象的指针。
结果是,对这些对象的特性或(在对象是数组和集合的情况下)对其元素的访问通常是令人满意的。就为
了实现这种用途,
EL
提供了两种不同的存取器(点运算符(
.
)和方括号运算符(
[]
)),也支持通过
EL
操作特性和元素。
点运算符通常用于访问对象的特性。例如,在表达式
${user.firstName}
中,使用点运算符来访问
user
标识符所引用对象的名为
firstName
的特性。
EL
使用
Java bean
约定访问对象特性,因此必须定义这
个特性的
getter
方法(通常是名为
getFirstName()
的方法),以便表达式正确求值。当被访问的特性
本身是对象时,可以递归地应用点运算符。例如,如果我们虚构的
user
对象有一个实现为
Java
对象的
address
特性,那么也可以用点运算符来访问这个对象的特性。例如,表达式
${user.address.city}
将
会返回这个地址对象嵌套的
city
特性。
方括号运算符用来检索数组和集合的元素。在数组和有序集合(也即,实现了
java.util.List
接口的集
合)的情况下,把要检索的元素的下标放在方括号中。例如,表达式
${urls[3]}
返回
urls
标识符所引
用的数组或集合的第四个元素(和
Java
语言以及
JavaScript
中一样,
EL
中的下标是从零开始的)。
对于实现
java.util.Map
接口的集合,方括号运算符使用关联的键查找存储在映射中的值。在方括号中
指定键,并将相应的值作为表达式的值返回。例如,表达式
${commands["dir"]}
返回与
commands
标
识符所引用的
Map
中的
"dir"
键相关联的值。
对于上述两种情况,都可允许表达式出现在方括号中。对嵌套表达式求值的结果将被作为下标或键,用来
检索集合或数组的适当元素。和点运算符一样,方括号运算符也可以递归应用。这使得
EL
能够从多维数
组、嵌套集合或两者的任意组合中检索元素。此外,点运算符和方括号运算符还可以互操作。例如,如果
数组的元素本身是对象,则可以使用方括号运算符来检索该数组的元素,并结合点运算符来检索该元素的
一个特性(例如
${urls[3].protocol}
)。
假定
EL
充当指定动态属性值的简化语言,
EL
存取器有一个有趣的功能(与
Java
语言的存取器不同),
那就是它们在应用于
null
时不抛出异常。如果应用
EL
存取器的对象(例如,
${foo.bar}
和
${foo["bar"]}
中的
foo
标识符)是
null
,那么应用存取器的结果也是
null
。事实证明,在大多数情
况下,这是一个相当有用的行为,不久您就会了解这一点。
最后,点运算符和方括号运算符可能实现某种程度的互换。例如,也可以使用
${user["firstName"]}
来
检索
user
对象的
firstName
特性,正如可以用
${commands.dir}
获取与
commands
映射中的
"dir"
键相关联的值一样。
运算符
EL
还可以通过使用标识符和存取器,遍历包含应用程序数据(通过限制了作用域的变量公开)或关于环
境的信息(通过
EL
隐式对象)的对象层次结构。但是,只是访问这些数据,通常不足以实现许多
JSP
应
用程序所需的表示逻辑。
最终,
EL
还包括了几个用来操作和比较
EL
表达式所访问数据的运算符。表
2
中汇总了这些运算符。
表
2. EL
运算符
类别
运算符
算术运算符
+
、
-
、
*
、
/
(或
div
)和
%
(或
mod
)
关系运算符
==
(或
eq
)、
!=
(或
ne
)、
<
(或
lt
)、
>
(或
gt
)、
<=
(或
le
)和
>=
(或
ge
)
逻辑运算符
&&
(或
and
)、
||
(或
or
)和
!
(或
not
)
验证运算符
empty
算术运算符支持数值的加法、减法、乘法和除法。还提供了一个求余运算符。注:除法和求余运算符都有
替代的、非符号的名称(为的是与
XPath
保持一致)。清单
5
中显示了一个演示算术运算符用法的示例
表达式。对几个
EL
表达式应用算术运算符的结果是将该算术运算符应用于这些表达式返回的数值所得的
结果。
清单
5.
利用算术运算符的
EL
表达式
${item.price * (1 + taxRate[user.address.zipcode])}
关系运算符允许比较数字或文本数据。比较的结果作为布尔值返回。逻辑运算符允许合并布尔值,返回新
的布尔值。因此,可以将
EL
逻辑运算符应用于嵌套的关系或逻辑运算符的结果,如清单
6
所示。
清单
6.
利用关系和逻辑运算符的
EL
表达式
${(x >= min) && (x <= max)}
最后一种
EL
运算符是
empty
,它对于验证数据特别有用。
empty
运算符采用单个表达式作为其变量(也
即,
${empty input}
),并返回一个布尔值,该布尔值表示对表达式求值的结果是不是
“
空
”
值。求值结果
为
null
的表达式被认为是空,即无元素的集合或数组。如果参数是对长度为零的
String
求值所得的结
果,则
empty
运算符也将返回
true
。
表
3
显示了
EL
运算符的优先级。正如清单
5
和
6
所示,可以用圆括号对表达式分组,高于普通的优
先级规则。
表
3. EL
运算符优先级(自顶到底,从左到右)
[]
,
.
()
unary
-
、
not
、
!
、
empty
*
、
/
、
div
、
%
、
mod
+
、
binary
-
()
<</code>
、
>
、
<=
、
>=
、
lt
、
gt
、
le
、
ge
==
、
!=
、
eq
、
ne
&&
、
and
||
、
or
文字
在
EL
表达式中,数字、字符串、布尔值和
null
都可以被指定为文字值。字符串可以用单引号或双引号
定界。布尔值被指定为
true
和
false
。
Taglib
伪指令
正如我们先前讨论的,
JSTL 1.0
包括四个定制标记库。为了演示
JSTL
标记和表达式语言的交互,我们
将研究几个来自
JSTL
core
库的标记。和使用任何
JSP
定制标记库一样,必须在您想要使用这个库标记
的任何页面中包括
taglib
伪指令。清单
7
显示了用于这个特定库的伪指令。
清单
7.
用于
JSTL core
库
EL
版本的
taglib
伪指令
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
实际上,对应于
JSTL
core
库的
taglib
伪指令有两种,因为在
JSTL 1.0
中,
EL
是可选的。所有四
个
JSTL 1.0
定制标记库都有使用
JSP
表达式(而不是
EL
)指定动态属性值的备用版本。因为这些备
用库依赖于
JSP
的更传统的请求时属性值,所以它们被称为
RT
库,而那些使用表达式语言的则被称为
EL
库。开发人员用不同的
taglib
伪指令来区分每个库的这两个版本。清单
8
显示了使用
core
库的
RT
版本的伪指令。但是,由于现在我们讨论的重点是
EL
,所以首先需要这些伪指令。
清单
8.
用于
JSTL core
库
RT
版本的
taglib
伪指令
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>
变量标记
我们首先要考虑的
JSTL
定制标记是
<c:set>
操作。正如已经说明的,限制了作用域的变量在
JSTL
中
起关键作用,
<c:set>
操作提供基于标记的机制来创建和设置限制了作用域的变量。清单
9
中显示了该
操作的语法,其中
var
属性指定了限制了作用域的变量的名称,
scope
属性表明了该变量驻留在哪个作
用域中,
value
属性指定了分配给该变量的值。如果指定变量已经存在,则简单地将所指明的值赋给它。
如果不存在,则创建新的限制了作用域的变量,并用该值初始化这个变量。
清单
9. <c:set>
操作的语法
<c:set var="
name
" scope="
scope
" value="
expression
"/>
scope
属性是可选的,其缺省值是
page
。
清单
10
中显示了
<c:set>
的两个示例。在第一个示例中,将会话作用域变量设置成
String
值。在第
二个示例中,用表达式来设置数值:将页面作用域内名为
square
的变量赋值为名为
x
的请求参数的值
的平方。
清单
10. <c:set>
操作示例
<c:set var="timezone" scope="session" value="CST"/>
<c:set var="square" value="${param['x'] * param['x']}"/>
您还可以将限制了作用域的变量的值指定为
<c:set>
操作的主体内容,而不是使用属性。使用这种方法,
您可以重新编写清单
10
中的第一个示例,如清单
11
所示。此外,正如我们马上可以看到的,
<c:set>
标记的主体内容本身也可以使用定制标记。
<c:set>
主体内生成的所有内容都将作为一个
String
值赋给
指定变量。
清单
11.
通过主体内容指定
<c:set>
操作的值
<c:set var="timezone" scope="session">CST</c:set>
JSTL core
库包含第二个用于管理限制了作用域的变量的标记
―
<c:remove>
。顾名思义,
<c:remove>
操
作是用来删除限制了作用域的变量的,它获取两个属性。
var
属性指定待删除变量的名称,
scope
属性是
可选的,它表示待删除变量来自哪个作用域,缺省为
page
,如清单
12
所示。
清单
12. <c:remove>
操作示例
<c:remove var="timezone" scope="session"/>
输出
尽管
<c:set>
操作允许将表达式结果赋给限制了作用域的变量,但开发人员通常会希望只显示表达式的
值,而不存储它。
JSTL
<c:out>
定制标记承担这一任务,其语法如清单
13
所示。该标记对由其
value
属性指定的表达式进行求值,然后打印结果。如果指定了可选属性
default
,那么,在对
value
属性的
表达式求值所得结果为
null
或空
String
的情况下,
<c:out>
将打印其值。
清单
13. <c:out>
操作的语法
<c:out value="
expression
" default="
expression
" escapeXml="
boolean
"/>
escapeXml
属性也是可选的。它控制当用
<c:out>
标记输出诸如
“<”
、
“>”
和
“&”
之类的字符(在
HTML
和
XML
中具有特殊意义)时是否应该进行转义。如果将
escapeXml
设置为
true
,则会自动将这些字符
转换成相应的
XML
实体(此处提到的字符分别转换成
<
、
>
和
&
)。
例如,假定有一个名为
user
的会话作用域变量,它是一个类的实例,该类为用户定义了两个特性:
username
和
company
。每当用户访问站点时,这个对象被自动分配给会话,但直到用户实际登录后,才
会设置这两个特性。假定是这种方案,请考虑清单
14
中的
JSP
片段。在用户登录之后,这个片段将显
示单词
“Hello”
,其后是他/她的用户名和一个惊叹号。但是,在用户登录之前,由这个片段生成的内容则
是短语
“Hello Guest!”
。在这种情况下,因为
username
特性还有待初始化,所以
<c:out>
标记将转而
打印出
default
属性的值(即字符串
“Guest”
)。
清单
14.
带缺省内容的
<c:out>
操作示例
Hello <c:out value="${user.username}" default=="Guest"/>!
接下来,考虑清单
15
,它使用了
<c:out>
标记的
escapeXml
属性。如果在这种情况下已经将
company
特性设置成
Java
String
值
"Flynn & Sons"
,那么,实际上该操作生成的内容将是
Flynn & Sons
。
如果这个操作是生成
HTML
或
XML
内容的
JSP
页面的一部分,那么,这个字符串中间的
“&”
符号最终
可能被解释为
HTML
或
XML
控制字符,从而妨碍了对该内容的显示或解析。但是,如果将
escapeXml
属性值设置成
true
,则所生成的内容将是
Flynn & Sons
。浏览器或解析器不会因在解释时遇到这
种内容而出问题。假定
HTML
和
XML
是
JSP
应用程序中最常见的内容类型,所以
escapeXml
属性的
缺省值是
true
就不足为奇了。
清单
15.
禁用转义的
<c:out>
操作示例
<c:out value="${user.company}" escapeXml=="false"/>
用缺省值设置变量
除了简化动态数据的显示之外,当通过
<c:set>
设置变量值时,
<c:out>
指定缺省值的能力也很有用。
正如
清单
11
所示,用来赋给限制了作用域的变量的值可以指定为
<c:set>
标记的主体内容,也可以通
过其值属性来指定。通过将
<c:out>
操作嵌套在
<c:set>
标记的主体内容中,变量赋值就可以利用其缺
省值能力。
清单
16
中说明了这种方法。外部
<c:set>
标记的行为非常简单:它根据其主体内容设置会话作用域
timezone
变量的值。但是,在这种情况下,主体内容是通过
<c:out>
操作生成的。这个嵌套操作的值属
性是表达式
${cookie['tzPref'].value}
,它尝试通过
cookie
隐式对象返回名为
tzPref
的
cookie
值。(
cookie
隐式对象将
cookie
名称映射到相应的
Cookie
实例,这意味着必须通过对象的
value
特
性使用点运算符来检索储存在
cookie
中的实际数据。)
清单
16.
合并
<c:set>
和
<c:out>
以提供缺省变量值
<c:set var="timezone" scope=="session">
<c:out value="${cookie['tzPref'].value}" default=="CST"/>
</c:set>
但是,请考虑以下情况,用户是第一次尝试使用这段代码的
Web
应用程序。结果是,请求中没有提供名
为
tzPref
的
cookie
。这意味着使用隐式对象的查找将返回
null
,在这种情况下整个表达式将返回
null
。因为对
<c:out>
标记的
value
属性求值的结果是
null
,所以
<c:out>
标记会转而输出对其
default
属性求值的结果。在这里是字符串
CST
。因此,实际的结果是将
timezone
限制了作用域的变量
设置成用户的
tzPref
cookie
中存储的时区,或者,如果没有,则使用缺省时区
CST
。
EL
和
JSP 2.0
目前,表达式语言仅可用于指定
JSTL
定制标记中的动态属性值。但
JSTL 1.0
表达式语言的一个扩展已
经被提出,会把它包括到
JSP 2.0
中去,眼下正在进行最后评审。这个扩展将允许开发人员通过自己的
定制标记来使用
EL
。页面作者将可以在目前允许使用
JSP
表达式的任何地方使用
EL
表达式,譬如将动
态值插入模板文本中:
<p>Your preferred time zone is ${timezone}</p>
。
这个
JSP 2.0
功能(就象
JSTL
本身一样)将支持页面作者进一步减少对
JSP
编制脚本元素的依赖,
从而改进
JSP
应用程序的可维护性。
结束语
EL
(与四个
JSTL
定制标记库提供的操作结合起来)允许页面作者不使用脚本元素即可实现表示层逻辑。
例如,对比本文开头
清单
1
中的
JSP
代码和清单
17
中显示的通过
JSTL
实现的同样功能。(
JSTL
core
库中其余的标记,包括
<c:choose>
及其子标记,将在本系列的下一篇文章中讨论。)尽管显然执
行了条件逻辑,但是
JSTL
版本中没有
Java
语言源代码,并且标记之间的关系(尤其是关于嵌套需求)
对于任何精通
HTML
语法的人都应该是熟悉的。
清单
17.
合并
<c:set>
和
<c:out>
以提供缺省变量值
<c:choose><c:when test="${user.role == 'member'}">
<p>Welcome, member!</p>
</c:when><c:otherwise>
<p>Welcome, guest!</p>
</c:otherwise></c:choose>
通过提供大多数
Web
应用程序常用功能的标准实现,
JSTL
有助于加速开发周期。与
EL
结合起来,
JSTL
可以不需要对表示层程序编写代码,这极大地简化了
JSP
应用程序的维护。
参考资料
使用定制标记控制
JSP
页面
JSP
标记库:着意设计的更好的可用性
Sun
的
JSP
标准标记库主页
是了解关于
JSTL
的更多信息的良好起点。
JSTL 1.0
规范
是关于
EL
和四个
JSTL
标记库的最终权威文本。
Jakarta Taglibs
项目是
JSTL 1.0
参考实现的起源。
Shawn Bayern
所著的
JSTL in Action
(
Manning Publications Co.
,
2002
年)提供了对
所有
JSTL
功能的精彩论述,作者是该参考实现的领导。
David Geary
是
Java
技术方面很受欢迎的作者,他也写了一本关于
JSTL
的书,书名是
Core
JSTL
。
JSPTags.com
是
JSP
技术参考资料的目录,它尤其专注于定制标记库。
通过
Jeff Wilson
精彩的文章
“
使用定制标记控制
JSP
页面
”
(
developerworks
,
2002
年
1
月)了解关于定制标记库的一切。
Noel Bergman
的文章
“
JSP
标记库:着意设计的更好的可用性
”
(
developerworks
,
2001
年
12
月)向您展示了声明性标记是如何帮助提高
JSP
页面的可用性的。
有关
EcmaScript
的更多详细信息,请参阅
Sing Li
的
“
快速上手
Java
编程
”
(
developerworks
,
2001
年
7
月)。
在
developerworks
Java
技术专区
可以找到多达数百篇的
Java
技术参考资料。
流控制
由于可以用
EL
替代
JSP
表达式来指定动态属性值,因此页面创作人员无需使用脚本编制元素。因为脚
本编制元素可能是引起
JSP
页面中维护问题的主要原因,所以
JSTL
的主要优点就在于提供了这样简单
(且标准)的替代方法。
EL
从
JSP
容器检索数据,遍历对象层次结构,然后对结果执行简单的操作。不过,除了访问和操作数据
之外,
JSP
脚本编制元素的另一个常见用法是流控制。尤其是,页面创作人员常借助
scriptlet
来实现迭
代或条件内容。然而,因为这样的操作超出了
EL
的能力,所以
core
库提供了几个定制操作来管理流控
制,其形式有
迭代
、
条件化
和
异常处理
。
迭代
在
Web
应用程序环境中,迭代主要用于访存和显示数据集,通常是以列表或表中的一系列行的形式显示。
实现迭代内容的主要
JSTL
操作是
<c:forEach>
定制标记。该标记支持两种不同样式的迭代:整数范围
上的迭代(类似
Java
语言的
for
语句)和集合上的迭代(类似
Java
语言的
Iterator
和
Enumeration
类)。
进行整数范围迭代用到了清单
1
中所示的
<c:forEach>
标记的语法。
begin
和
end
属性要么是静态
整数值,要么是可以得出整数值的表达式。它们分别指定迭代索引的初始值以及迭代索引的终止值。当使
用
<c:forEach>
在整数范围内进行迭代时,这两个属性是必需的,而其它所有属性都是可选的。
清单
1.
通过
<c:forEach>
操作进行数字迭代的语法
<c:forEach var="
name
" varStatus="
name
"
begin="
expression
" end="
expression
" step="
expression
">
body content
</c:forEach>
当出现
step
时,它也必须是整数值。它指定每次迭代后索引的增量。这样,迭代索引从
begin
属性的
值开始,以
step
属性的值为增量进行递增,在迭代索引超过
end
属性的值时停止迭代。注:如果省略
了
step
属性,那么步长缺省为
1
。
如果指定了
var
属性,那么将会创建一个带有指定名称的并限定了作用域的变量,并将每次迭代的当前索
引值赋给该变量。这一限定了作用域的变量具有嵌套式可视性
―
只可以在
<c:forEach>
标记体内对其
进行访问。(我们很快将讨论可选属性
varStatus
的用法。)清单
2
显示了对一组固定整数值进行迭代
的
<c:forEach>
操作示例。
清单
2.
使用
<c:forEach>
标记来生成表列数据,这些数据对应于某一范围内的数值
<table>
<tr><th>Value</th>
<th>Square</th></tr>
<c:forEach var="x" begin="0" end="10" step="2">
<tr><td><c:out value="${x}"/></td>
<td><c:out value="${x * x}"/></td></tr>
</c:forEach>
</table>
如图
3
中所示,上面的示例代码生成了一张表,显示前五个偶数及其平方。这是通过将
begin
和
step
属
性值指定为
2
,而将
end
属性值指定为
10
实现的。此外,用
var
属性创建用于存储索引值的限定了作
用域的变量,
<c:forEach>
标记体内引用了该变量。尤其是,使用了一对
<c:out>
操作来显示索引及其
平方,其中索引的平方是使用一个简单的表达式计算得来的。
图
3.
清单
2
的输出
在对集合的成员进行迭代时,用到了
<c:forEach>
标记的另一个属性:
items
属性,清单
3
中显示了
该属性。当使用这种形式的
<c:forEach>
标记时,
items
属性是唯一必需的属性。
items
属性的值应
该是一个集合,对该集合的成员进行迭代,通常使用
EL
表达式指定值。如果变量名称是通过
<c:forEach>
标记的
item
属性指定的,那么对于每次迭代该已命名变量都将被绑定到集合后续元素上。
清单
3.
通过
<c:forEach>
操作对集合进行迭代的语法
<c:forEach var="
name
" items="
expression
" varStatus="
name
"
begin="
expression
" end="
expression
" step="
expression
">
body content
</c:forEach>
<c:forEach>
标记支持
Java
平台所提供的所有标准集合类型。此外,您可以使用该操作来迭代数组(包
括基本类型数组)中的元素。表
1
列出了
items
属性所支持的所有值。正如表的最后一行所指出的那样,
JSTL
定义了它自己的接口
javax.servlet.jsp.jstl.sql.Result
,用来迭代
SQL
查询的结果。(我
们将在本系列后面的文章中详细讨论这一功能。)
表
1. <c:forEach>
标记的
items
属性所支持的集合
items
的值
所产生的
item
值
java.util.Collection
调用
iterator()
所获得的元素
java.util.Map
java.util.Map.Entry
的实例
java.util.Iterator
迭代器元素
java.util.Enumeration
枚举元素
Object
实例数组
数组元素
基本类型值数组
经过包装的数组元素
用逗号定界的
String
子字符串
javax.servlet.jsp.jstl.sql.Result
SQL
查询所获得的行
可以使用
begin
、
end
和
step
属性来限定在迭代中包含集合中哪些元素。和通过
<c:forEach>
进行
数字迭代的情形一样,在迭代集合中的元素时同样要维护一个迭代索引。
<c:forEach>
标记实际上只处
理那些与索引值相对应的元素,这些索引值与指定的
begin
、
end
和
step
值相匹配。
清单
4
显示了用来迭代集合的
<c:forEach>
标记。对于该
JSP
代码段,
entryList
这一限定了作用
域的变量被设置成了
Entry
对象列表(确切的说,
ArrayList
)。
<c:forEach>
标记依次处理列表中
的每个元素,将其赋给一个限定了作用域的变量
blogEntry
,然后生成两个表行
―
一个用于
Weblog
项的
title
,另一个则用于该项
text
。这些特性是通过一对带有相应
EL
表达式的
<c:out>
操作从
blogEntry
变量检索得到的。注:由于
Weblog
项的标题和文本都可能包含
HTML
标记,因此这两个
<c:out>
标记的
escapeXml
属性都被设置成了
false
。图
4
显示了结果。
清单
4.
使用
<c:forEach>
标记显示表示给定日期的
Weblog
项
<table>
<c:forEach items="${entryList}" var="blogEntry">
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图
4.
清单
4
的输出
不论是对整数还是对集合进行迭代,
<c:forEach>
剩余的属性
varStatus
所起的作用相同。和
var
属
性一样,
varStatus
用于创建限定了作用域的变量。不过,由
varStatus
属性命名的变量并不存储当前
索引值或当前元素,而是赋予
javax.servlet.jsp.jstl.core.LoopTagStatus
类的实例。该类定义了
一组特性,它们描述了迭代的当前状态,表
2
中列出了这些特性。
表
2. LoopTagStatus
对象的特性
特性
Getter
描述
current
getCurrent()
当前这次迭代的(集合中的)项
index
getIndex()
当前这次迭代从
0
开始的迭代索引
count
getCount()
当前这次迭代从
1
开始的迭代计数
first
isFirst()
用来表明当前这轮迭代是否为第一次迭代的标志
last
isLast()
用来表明当前这轮迭代是否为最后一次迭代的标志
begin
getBegin()
begin
属性值
end
getEnd()
end
属性值
step
getStep()
step
属性值
清单
5
显示了关于如何使用
varStatus
属性的一个示例。这个示例修改了清单
4
中的代码,将
Weblog
项的编号添加到显示
Weblog
标题(
title
)的表行。它是通过为
varStatus
属性指定一个值,
然后访问所生成的限定了作用域的变量的
count
特性来实现这一点的。结果显示在图
5
中。
清单
5.
使用
varStatus
属性来显示
Weblog
项的数目
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<tr><td align="left" class="blogTitle">
<c:out value="${status.count}"/>.
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图
5.
清单
5
的输出
除
<c:forEach>
以外,
core
库还提供了另一个迭代标记:
<c:forTokens>
。
JSTL
的这个定制操作
与
Java
语言的
StringTokenizer
类的作用相似。清单
6
中显示的
<c:forTokens>
标记除了比
<c:forEach>
的面向集合版本多一个属性之外,其它属性都相同。对于
<c:forTokens>
而言,通过
items
属性指定要标记化的字符串,而通过
delims
属性提供用于生成标记的一组定界符。和
<c:forEach>
的
情形一样,可以使用
begin
、
end
和
step
属性将要处理的标记限定为那些与相应索引值相匹配的标记。
清单
6.
使用
<c:forTokens>
操作来迭代字符串标记的语法
<c:forTokens var="
name
" items="
expression
"
delims="
expression
" varStatus="
name
"
begin="
expression
" end="
expression
" step="
expression
">
body content
</c:forTokens>
条件化
对于包含动态内容的
Web
页面,您可能希望不同类别的用户看到不同形式的内容。例如,在我们的
Weblog
中,访问者应该能够阅读各项,也许还应该能够提交反馈,但只有经过授权的用户才能公布新项,
或编辑已有内容。
在同一个
JSP
页面内实现这样的功能,然后使用条件逻辑来根据每条请求控制所显示的内容,这样做常
常能够改善实用性和软件维护。
core
库提供了两个不同的条件化标记
―
<c:if>
和
<c:choose>
―
来
实现这些功能。
<c:if>
是这两个操作中较简单的一个,它简单地对单个测试表达式进行求值,接下来,仅当对表达式求
出的值为
true
时,它才处理标记的主体内容。如果求出的值不为
true
,就忽略该标记的主体内容。如
清单
7
所示,
<c:if>
可以通过其
var
和
scope
属性(它们所起的作用和在
<c:set>
中所起的作用
一样)选择将测试结果赋给限定了作用域的变量。当测试代价非常高昂时,这种能力尤为有用:可以将结
果高速缓存在限定了作用域的变量中,然后在随后对
<c:if>
或其它
JSTL
标记的调用中检索该结果。
清单
7. <c:if>
条件操作的语法
<c:if test="
expression
" var="
name
" scope="
scope
">
body content
</c:if>
清单
8
显示了与
<c:forEach>
标记的
LoopTagStatus
对象的
first
特性一起使用的
<c:if>
。如图
6
中所示,在这种情况下,只在
Weblog
项的第一项上显示这组项的创建日期,而不在任何其它项前面
重复该日期。
清单
8.
使用
<c:if>
来为
Weblog
项显示日期
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<c:out value="${blogEntry.created}"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图
6.
清单
8
的输出
如清单
8
所示,
<c:if>
标记为条件化内容的一些简单情形提供了一种非常简洁的表示法。对于需要进
行互斥测试来确定应该显示什么内容的情况下,
JSTL
core
库还提供了
<c:choose>
操作。清单
9
中显
示了
<c:choose>
的语法。
清单
9. <c:choose>
操作的语法
<c:choose>
<c:when test="
expression
">
body content
</c:when>
...
<c:otherwise>
body content
</c:otherwise>
</c:choose>
每个要测试的条件都由相应的
<c:when>
标记来表示,至少要有一个
<c:when>
标记。只会处理第一个其
test
值为
true
的
<c:when>
标记体内的内容。如果没有一个
<c:when>
测试返回
true
,那么会处理
<c:otherwise>
标记的主体内容。注:尽管如此,
<c:otherwise>
标记却是可选的;
<c:choose>
标
记至多可有一个嵌套的
<c:otherwise>
标记。如果所有
<c:when>
测试都为
false
,而且又没有给出
<c:otherwise>
操作,那么不会处理任何
<c:choose>
标记的主体内容。
清单
10
显示了运用
<c:choose>
标记的示例。在这里,检索请求对象而获得协议信息(通过
EL
的
pageContext
隐式对象),并用简单的字符串比较对协议信息进行测试。根据这些测试的结果,会显示相
应的文本消息。
清单
10.
使用
<c:choose>
进行内容条件化
<c:choose>
<c:when test="${pageContext.request.scheme eq 'http'}">
This is an insecure Web session.
</c:when>
<c:when test="${pageContext.request.scheme eq 'https'}">
This is a secure Web session.
</c:when>
<c:otherwise>
You are using an unrecognized Web protocol. How did this happen?!
</c:otherwise>
</c:choose>
异常处理
最后一个流控制标记是
<c:catch>
,它允许在
JSP
页面内进行初级的异常处理。更确切地说,在该标记
的主体内容中产生的任何异常都会被捕获并被忽略(即,不会调用标准的
JSP
错误处理机制)。然而,
如果产生了一个异常并且已经指定了
<c:catch>
标记的可选属性
var
,那么会将异常赋给(具有页面作
用域的)指定的变量,这使得能够在页面自身内部进行定制错误处理。清单
11
显示了
<c:catch>
的语
法(稍后在
清单
18
中给出一个示例)。
清单
11. <c:catch>
操作的语法
<c:catch var="
name
">
body content
</c:catch>
回页首
URL
操作
JSTL
core
库中的其余标记主要是关于
URL
。这些标记中的第一个被适当地命名为
<c:url>
标记,用于
生成
URL
。尤其是,
<c:url>
提供了三个功能元素,它们在为
J2EE Web
应用程序构造
URL
时特别
有用:
在前面附加当前
servlet
上下文的名称
为会话管理重写
URL
请求参数名称和值的
URL
编码
清单
12
显示了
<c:url>
标记的语法。
value
属性用来指定基本
URL
,然后在必要时标记对其进行转
换。如果这个基本
URL
以一个斜杠开始,那么会在它前面加上
servlet
的上下文名称。可以使用
context
属性提供显式的上下文名称。如果省略该属性,那么就使用当前
servlet
上下文的名称。这一点特别有用,
因为
servlet
上下文名称是在部署期间而不是开发期间决定的。(如果这个基本
URL
不是以斜杠开始的,
那么就认为它是一个相对
URL
,这时就不必添加上下文名称。)
清单
12. <c:url>
操作的语法
<c:url value="
expression
" context="
expression
"
var="
name
" scope="
scope
">
<c:param name="
expression
" value="
expression
"/>
...
</c:url>
URL
重写是由
<c:url>
操作自动执行的。如果
JSP
容器检测到一个存储用户当前会话标识的
cookie
,
那么就不必进行重写。但是,如果不存在这样的
cookie
,那么
<c:url>
生成的所有
URL
都会被重写以
编码会话标识。注:如果在随后的请求中存在适当的
cookie
,那么
<c:url>
将停止重写
URL
以包含该
标识。
如果为
var
属性提供了一个值(还可以同时为
scope
属性提供一个相应的值,这是可选的),那么将生
成的
URL
赋值给这个限定了作用域的指定变量。否则,将使用当前的
JspWriter
输出生成的
URL
。这
种直接输出其结果的能力允许
<c:url>
标记作为值出现,例如,作为
HTML
<a>
标记的
href
属性的值,
如清单
13
中所示。
清单
13.
生成
URL
作为
HTML
标记的属性值
<a href="<c:url value='/content/sitemap.jsp'/>">View sitemap</a>
最后,如果通过嵌套
<c:param>
标记指定了任何请求参数,那么将会使用
HTTP GET
请求的标准表示
法将它们的名称和值添加到生成的
URL
后面。此外,还进行
URL
编码:为了生成有效的
URL
,将对这
些参数的名称或值中出现的任何字符适当地进行转换。清单
14
演示了
<c:url>
的这种行为。
清单
14.
生成带请求参数的
URL
<c:url value="/content/search.jsp">
<c:param name="keyword" value="${searchTerm}"/>
<c:param name="month" value="02/2003"/>
</c:url>
清单
14
中的
JSP
代码被部署到一个名为
blog
的
servlet
上下文,限定了作用域的变量
searchTerm
的值被设置为
"core library"
。如果检测到了会话
cookie
,那么清单
14
生成的
URL
将类似于清单
15
中的
URL
。注:在前面添加上下文名称,而在后面附加请求参数。此外,
keyword
参数值中的空格
和
month
参数值中的斜杠都被按照
HTTP GET
参数的需要进行了编码(确切地说,空格被转换成了
+
,
而斜杠被转换成了
%2F
序列)。
清单
15.
有会话
cookie
时生成的
URL
/blog/content/search.jsp?keyword=foo+bar&month=02%2F2003
当没有会话
cookie
时,生成的结果如清单
16
中所示。同样,
servlet
上下文被添加到了前面,而
URL
编码的请求参数被附加到了后面。不过,除此以外还重写了基本
URL
以包含指定的会话标识。当浏览器
发送用这种方式重写的
URL
请求时,
JSP
容器自动抽取会话标识,并将请求与相应的会话进行关联。这
样,需要会话管理的
J2EE
应用程序就无需依赖由应用程序用户启用的
cookie
了。
清单
16.
没有会话
cookie
时生成的
URL
/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
?keyword=foo+bar&month=02%2F2003
回页首
导入内容
JSP
有两种内置机制可以将来自不同
URL
的内容合并到一个
JSP
页面:
include
伪指令和
<jsp:include>
操作。不过,不管是哪种机制,要包含的内容都必须属于与页面本身相同的
Web
应用程
序(或
servlet
上下文)。两个标记之间的主要区别在于:
include
伪指令在页面编译期间合并被包含
的内容,而
<jsp:include>
操作却在请求处理
JSP
页面时进行。
从本质上讲,
core
库的
<c:import>
操作是更通用、功能更强大的
<jsp:include>
版本(好像是
<jsp:include>
“
服用了兴奋剂
”
的)。和
<jsp:include>
一样,
<c:import>
也是一种请求时操作,它
的基本任务就是将其它一些
Web
资源的内容插入
JSP
页面中。如清单
17
中所示,它的语法非常类似
于
<c:url>
的语法。
清单
17. <c:import>
操作的语法
<c:import url="
expression
" context="
expression
"
charEncoding="
expression
" var="
name
" scope="
scope
">
<c:param name="
expression
" value="
expression
"/>
...
</c:import>
通过
url
属性指定将要导入内容的
URL
,这个属性是
<c:import>
的唯一一个必选属性。这里允许使用
相对
URL
,并且根据当前页面的
URL
来解析这个相对
URL
。但是,如果
url
属性的值以斜杠开始,那
么它就被解释成本地
JSP
容器内的绝对
URL
。如果没有为
context
属性指定值,那么就认为这样的绝
对
URL
引用当前
servlet
上下文内的资源。如果通过
context
属性显式地指定了上下文,那么就根据
指定的
servlet
上下文解析绝对(本地)
URL
。
但
<c:import>
操作并不仅仅限于访问本地内容。也可以将包含协议和主机名的完整
URI
指定为
url
属性的值。实际上,协议甚至不仅局限于
HTTP
。
<c:import>
的
url
属性值可以使用
java.net.URL
类
所支持的任何协议。清单
18
中显示了这种能力。
其中,
<c:import>
操作用来包含通过
FTP
协议访问的文档内容。此外,还使用了
<c:catch>
操作,
以便在本地处理
FTP
文件传送期间可能发生的任何错误。错误处理是这样实现的:使用
<c:catch>
的
var
属性为异常指定一个限定了作用域的变量,然后使用
<c:if>
检查其值。如果产生了异常,那么就会
对那个限定了作用域的变量进行赋值:如清单
18
中的
EL
表达式所显示的那样,该变量的值将
不
会为
空。由于
FTP
文档的检索将会失败,因此会显示有关这种情况的错误消息。
清单
18.
将
<c:import>
与
<c:catch>
相结合的示例
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
Sorry, the remote content is not currently available.
</c:if>
<c:import>
操作的最后两个(可选的)属性是
var
和
scope
。
var
属性会导致从指定
URL
获取的
内容(作为
String
值)被存储在一个限定了作用域的变量中,而不是包含在当前
JSP
页面中。
scope
属
性控制该变量的作用域,缺省情况下是页面作用域。如同我们在今后的文章中将要看到的那样,
JSTL
xml
库中的标记利用了
<c:import>
这种能力,即将整个文档存储在一个限定了作用域的变量中。
还要注意的是,可以使用(可选的)嵌套的
<c:param>
标记来为正在导入的
URL
指定请求参数。与在
<c:url>
中嵌套
<c:param>
标记一样,必要时也要对参数名称和参数值进行
URL
编码。
回页首
请求重定向
最后一个
core
库标记是
<c:redirect>
。该操作用于向用户的浏览器发送
HTTP
重定向响应,它是
JSTL
中与
javax.servlet.http.HttpServletResponse
的
sendRedirect()
方法功能相当的标记。清
单
19
中显示了该标记的
url
和
context
属性,它们的行为分别等同于
<c:import>
的
url
和
context
属性的行为,是嵌套任何
<c:param>
标记的结果。
清单
19. <c:redirect>
操作的语法
<c:redirect url="
expression
" context="
expression
">
<c:param name="
expression
" value="
expression
"/>
...
</c:redirect>
清单
20
显示了
<c:redirect>
操作,它用一个到指定错误页面的重定向代替了清单
18
中的错误消息。
在该示例中,
<c:redirect>
标记的用法与标准
<jsp:forward>
操作的用法类似。不过请回忆一下:通
过请求分派器进行转发是在服务器端实现的,而重定向却是由浏览器来执行的。从开发人员的角度来讲,
转发比重定向更有效率,但
<c:redirect>
操作却更灵活一些,因为
<jsp:forward>
只能分派到当前
servlet
上下文内的其它
JSP
页面。
清单
20.
响应异常的重定向
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
<c:redirect url="/errors/remote.jsp"/>
</c:if>
从用户的角度来看,主要区别在于重定向会更新浏览器所显示的
URL
,并因此影响书签的设置。转发却不
这样,它对最终用户是透明的。这样,选择
<c:redirect>
还是
<jsp:forward>
还取决于所期望的用户
体验。
回页首
结束语
JSTL
core
库含有多种通用的定制标记,广大的
JSP
开发人员都会使用这些标记。例如,
URL
和异常处
理标记很好地补充了现有的
JSP
功能,如
<jsp:include>
和
<jsp:forward>
操作、
include
伪指令
以及
page
伪指令的
errorpage
属性。迭代和条件操作使得无需脚本编制元素就能够实现复杂的表示逻
辑,尤其在将变量标记(
<c:set>
和
<c:remove>
)与
EL
相结合使用时更是如此。
在本系列的前几篇文章中,我们讨论了
JSTL
及其表达式语言(
EL
)。我们还研究了由
core
库定义的定
制标记。具体而言,在
“
JSTL
入门:表达式语言
”
中我们指出
EL
提供了一种简化语言,用于在
JSP
应
用程序中访问和操作数据并使该数据可被
JSTL
定制标记用作动态属性值。
core
库包含了一些定制标
记,用于管理限定了作用域的变量、显示
EL
值、实现迭代内容和条件内容以及与
URL
进行交互,这是
“
JSTL
入门:探讨
core
”
的主题。
我们接下来将讨论的
JSTL
库是
fmt
库。
fmt
库中的定制标记支持通过资源束对文本内容进行本地化,
并支持对数字和日期的显示和解析。这些标记利用在
java.util
和
java.text
包中实现的
Java
语言
的国际化
API
,因此如果您已经很熟悉诸如
ResourceBundle
、
Locale
、
MessageFormat
和
DateFormat
这样的类,那么您将发现
fmt
库中有很多方面值得称道。如果您不熟悉这些类,那么
fmt
库
的标记用直观的方式来封装国际化
API
,这种方式使您能够很容易将本地化功能合并到
JSP
应用程序中。
本地化
在
Java
语言国际化
API
中,影响数据本地化方式的因素主要有两个。一个是用户的
语言环境
,另一个
是用户的
时区
。语言环境表示某一特定区域或文化的语言习惯,包括日期、数字和货币金额的格式。一个
语言环境始终会有一种相关联的语言,在许多情况下这种语言是由多个语言环境共享的某种语言的方言。
例如,美国英语、英国英语、澳大利亚英语和加拿大英语都具有不同的英语语言环境,而法国、比利时、
瑞士和加拿大所用的法语方言则都具有不同的法语语言环境。
时区是数据本地化中的第二个因素,这仅仅是因为一些语言环境分布的地理区域很广。当您显示有关跨
洲语言环境(比如澳大利亚英语)的时间信息时,针对用户时区定制数据与对其进行正确格式化一样重要。
但是这就有了一个问题:应用程序如何确定用户的语言环境和时区?在
Java
应用程序的情况下,
JVM
能
够通过与本地操作系统进行交互来设置缺省语言环境和时区。虽然这种方法对于桌面应用程序而言可以正
常工作,但是它实际上并不适合于服务器端的
Java
应用程序,因为这种应用程序所处理的请求,可能来
自于距离该应用程序所驻留的服务器万里之遥的地方。
幸运的是,
HTTP
协议通过
Accept-Language
请求头将本地化信息从浏览器传递至服务器。许多
Web
浏览器允许用户定制他们的语言首选项,如图
1
所示。通常,那些没有为一种或多种首选语言环境提供显
式设置的浏览器会询问操作系统以确定在
Accept-Language
头中发送哪个值(或哪些值)。
servlet
规
范通过
javax.servlet.ServletRequest
类的
getLocale()
和
getLocales()
方法自动地利用
HTTP
协议的这一功能。
JSTL
fmt
库中的定制标记又会利用这些方法来自动地确定用户的语言环境,从
而相应地调整它们的输出。
图
1.
通过设置浏览器的语言首选项来选择语言环境
但遗憾的是,不存在将用户的时区从浏览器传输到服务器的标准
HTTP
请求头。因此,那些希望自己的
Web
应用程序对时间数据进行本地化的用户,将需要实现他们自己的机制,用来确定和跟踪特定于用户
的时区。例如,在本系列文章第
2
部分
“
JSTL
入门:探讨
core
”
中所介绍的
Weblog
应用程序包含了
一种将用户的时区首选项存储在
cookie
中的方式。
回页首
fmt
库
JSTL
fmt
库中的定制标记主要分成四组。第一组允许您设置本地化上下文,其它标记将在其中进行操作。
换句话说,这组标记允许页面作者显式地设置其它
fmt
标记在格式化数据时将要使用的语言环境和时区。
第二组和第三组标记分别支持对日期和数字进行格式化和解析。最后一组标记侧重于对文本消息进行本地
化。
既然我们已经有了些基本了解,那就让我们集中精力逐个研究这四组标记,并演示其用法。
本地化上下文标记
正如我们已经讨论过的那样,
JSTL
标记在格式化数据时所使用的语言环境往往是通过查看用户浏览器发
送的每个
HTTP
请求所包含的
Accept-Language
头来确定的。如果没有提供这样的头,那么
JSTL
提
供一组
JSP
配置变量,您可以设置这些变量以指定缺省的语言环境。如果尚未设置这些配置变量,那么
就使用
JVM
的缺省语言环境,该缺省语言环境是从
JSP
容器所运行的操作系统中获取的。
fmt
库提供了其自身的定制标记,以覆盖这个确定用户语言环境的过程:
<fmt:setLocale>
。正如下面
的代码片段所示,
<fmt:setLocale>
操作支持三个属性:
<fmt:setLocale value="
expression
"
scope="
scope
" variant="
expression
"/>
其中只有一个属性是必需的:
value
属性。该属性的值应当是命名该语言环境的一个字符串或者是
java.util.Locale
类的一个实例。语言环境名称是这样组成的:小写的两字母
ISO
语言代码,可选地,
后面可以跟下划线或连字符以及大写的两字母
ISO
国家或地区代码。
例如,
en
是英语的语言代码,
US
是美国的国家或地区代码,因此
en_US
(或
en-US
)将是美式英语
的语言环境名称。类似的,
fr
是法语的语言代码,
CA
是加拿大的国家或地区代码,因此
fr_CA
(或
fr-CA
)是加拿大法语的语言环境名称(请参阅
参考资料
以获取所有有效的
ISO
语言和国家或地区代码
的链接)。当然,由于国家或地区代码是可选的,因此
en
和
fr
本身就是有效的语言环境名称,适用于
不区别这些相应语言特定方言的应用程序。
<fmt:setLocale>
的可选属性
scope
用来指定语言环境的作用域。
page
作用域指出这项设置只适用于
当前页,而
request
作用域将它应用于请求期间访问的所有
JSP
页面。如果将
scope
属性设置成
session
,那么指定的语言环境被用于用户会话期间访问的所有
JSP
页面。值
application
指出该语
言环境适用于该
Web
应用程序所有
JSP
页面的全部请求和该应用程序所有用户的全部请求。
variant
属性(也是可选的)允许您进一步针对特定的
Web
浏览器平台或供应商定制语言环境。例如,
MAC
和
WIN
分别是
Apple Macintosh
和
Microsoft Windows
平台的变体名。
下面的代码片段说明了如何使用
<fmt:setLocale>
标记来显式指定用户会话的语言环境设置:
<fmt:setLocale value="fr_CA" scope="session"/>
JSP
容器处理完该
JSP
代码段之后,将忽略用户浏览器设置中所指定的语言首选项。
<fmt:setTimeZone>
操作像
<fmt:setLocale>
一样,可以用来设置其它
fmt
定制标记所使用的缺省时
区值。它的语法如下所示:
<fmt:setTimeZone value="
expression
"
var="
name
" scope="
scope
"/>
和
<fmt:setLocale>
一样,只有
value
属性是必需的,但是在本例中它应当是时区名或
java.util.TimeZone
类的实例。
遗憾的是,对于时区命名目前还没有任何被广泛接受的标准。因此您可以用于
<fmt:setTimezone>
标记
的
value
属性的时区名是特定于
Java
平台的。您可以通过调用
java.util.TimeZone
类的
getAvailableIDs()
静态方法来检索有效的时区名列表。示例包括
US/Eastern
、
GMT+8
和
Pacific/Guam
。
和
<fmt:setLocale>
的情况一样,您可以使用可选的
scope
属性来指出时区设置的作用域。下面的代
码演示了
<fmt:setTimeZone>
的用法,它用来指定适用于单个用户会话的时区:
<fmt:setTimeZone value="Australia/Brisbane" scope="session"/>
您还可以使用
<fmt:setTimeZone>
操作将
TimeZone
实例的值存储在限定了作用域的变量中。在本例
中,您可以使用
var
属性来命名限定了作用域的变量,用
scope
属性来指定该变量的作用域(例如,就
象这两个属性用在
<c:set>
和
<c:if>
操作中)。请注意,当您以这种方式使用
<fmt:setTimeZone>
操
作时,它唯一的副作用就是设置指定的变量。当指定
var
属性时,对于任何其它
JSTL
标记使用什么时
区,不会对
JSP
环境作任何更改。
这组中的最后一个标记是
<fmt:timeZone>
操作:
<fmt:timeZone value="
expression
">
body content
</fmt:timeZone>
和
<fmt:setTimeZone>
一样,您可以使用该标记来指定将由其它
JSTL
标记使用的时区。但是,
<fmt:timeZone>
操作的作用域仅限于其标记体内容。在
<fmt:timeZone>
标记体中,由标记的
value
属
性指定的时区覆盖了
JSP
环境中现有的任何其它时区设置。
和
<fmt:setTimeZone>
的情况一样,
<fmt:timeZone>
标记的
value
属性应当是时区名或者是
java.util.TimeZone
实例。后面的
清单
1
中提供了一个如何使用
<fmt:timeZone>
的示例。
日期标记
fmt
库包含了用来与日期和时间进行交互的两个标记:
<fmt:formatDate>
和
<fmt:parseDate>
。顾名
思义,
<fmt:formatDate>
用来格式化和显示日期和时间(数据
输出
),而
<fmt:parseDate>
用来解
析日期和时间值(数据
输入
)。
<fmt:formatDate>
的语法如下所示:
<fmt:formatDate value="
expression
"
timeZone="
expression
"
type="
field
" dateStyle="
style
"
timeStyle="
style
"
pattern="
expression
"
var="
name
" scope="
scope
"/>
只有
value
属性才是必需的。其值应当是
java.util.Date
类的实例,指定要进行格式化和显示的日期
和/或时间数据。
可选的
timeZone
属性指出将要显示哪个时区的日期和/或时间。如果没有显式地指定
timeZone
属性,
那么就使用周围任何
<fmt:timeZone>
标记所指定的时区。如果
<fmt:timeZone>
标记的主体部分没有
包含
<fmt:formatDate>
操作,那么就使用任何适用的
<fmt:setTimeZone>
操作所设置的时区。如果没
有相关的
<fmt:setTimeZone>
操作,那么就使用
JVM
的缺省时区(也就是,专为本地操作系统而设置
的时区)。
type
属性指出要显示指定的
Date
实例的哪些字段,应当是
time
、
date
或
both
。该属性的缺省值
是
date
,因此如果没有给出
type
属性,那么
<fmt:formatDate>
标记(名符其实)将只显示与
Date
实例相关的日期信息,这个信息用该标记的
value
属性指定。
dateStyle
和
timeStyle
属性分别指出应当如何格式化日期和时间信息。有效的样式有
default
、
short
、
medium
、
long
和
full
。缺省值自然是
default
,指出应当使用特定于语言环境的样式。
其它四个样式值的语义与
java.text.DateFormat
类定义的一样。
可以使用
pattern
属性来指定定制样式,而不必依赖于内置样式。给出定制样式后,该模式属性的值应
当是符合
java.text.SimpleDateFormat
类约定的模式字符串。这些模式基于用对应的日期和时间字段
代替模式内指定的字符。例如,模式
MM/dd/yyyy
表明应当显示用正斜杠分隔的两位数的月份和日期值以
及四位数的年份值。
如果指定了
var
属性,那就把包含格式化日期的
String
值指派给指定的变量。否则,
<fmt:formatDate>
标记将写出格式化结果。当指定了
var
属性后,
scope
属性指定所生成变量的作用
域。
清单
1
(它是本系列
第
2
部分
清单
8
的扩展)包含了
<fmt:formatDate>
标记的两种用法。在第一种
用法中,
<fmt:formatDate>
只用来显示第一个
weblog
项的创建时间戳记的日期部分。此外,为
dateStyle
属性指定了一个
full
值,这样一来所有的日期字段就将用一种特定于语言环境的格式进行显
示。
清单
1.
使用
<fmt:formatDate>
标记来显示日期和时间值
<table>
<fmt:timeZone value="US/Eastern">
<c:forEach items="${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<fmt:formatDate value=
"${blogEntry.created}" dateStyle="full"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
<font class="blogPosted">
[Posted <fmt:formatDate value="${blogEntry.created}"
pattern="h:mm a zz"/>]
</font>
</td></tr>
</c:forEach>
</fmt:timeZone>
</table>
在
<c:forEach>
循环体中,第二个
<fmt:formatDate>
操作只用来显示每个项的创建日期的时间部分。
在本例中,
pattern
属性用来控制时间值的格式化、并控制指定一位数的小时显示(如果可能的话)、
12
小时的时钟和缩写时区的输出。输出如图
2
所示:
图
2.
清单
1
中
en_US
语言环境的输出
更准确地说,用户浏览器设置指定首选项是英语时,就会产生图
2
中所示的输出。但是由于
<fmt:formatDate>
对用户语言环境敏感,所以浏览器首选项的改变将导致生成不同的内容。例如,当给
定的首选项是法语语言环境时,则结果会如图
3
所示:
图
3.
清单
1
中
fr_CA
语言环境的输出
<fmt:formatDate>
生成了
java.util.Date
实例的本地化字符串表示,而
<fmt:parseDate>
操作执行
相反的操作:给定一个表示日期和/或时间的字符串,它将生成相应的
Date
对象。
<fmt:parseDate>
操
作有两种格式,如下所示:
<fmt:parseDate value="
expression
"
type="
field
" dateStyle="
style
" timeStyle="
style
"
pattern="
expression
"
timeZone="
expression
" parseLocale="
expression
"
var="
name
" scope="
scope
"/>
<fmt:parseDate
type="
field
" dateStyle="
style
" timeStyle="
style
"
pattern="
expression
"
timeZone="
expression
" parseLocale="
expression
"
var="
name
" scope="
scope
">
body content
</fmt:parseDate>
对于第一种格式,只有
value
属性才是必需的,它的值应当是指定日期、时间或这两者组合的字符串。
对于第二种格式,没有必需的属性,表示要解析的值的字符串被指定为
<fmt:parseDate>
标记必需的标
记体内容。
type
、
dateStyle
、
timeStyle
、
pattern
和
timeZone
属性对
<fmt:parseDate>
和对
<fmt:formatDate>
起一样的作用,不同之处仅在于对于前者,它们控制日期值的解析而非显示。
parseLocale
属性用来指定一种语言环境,将根据这种语言环境来解析该标记的值,它应当是语言环境的
名称或
Locale
类的实例。
var
和
scope
属性用来指定限定了作用域的变量(作为
<fmt:parseDate>
的结果),将把
Date
对象
赋给该变量。如果没有给出
var
属性,则使用
Date
类的
toString()
方法将结果写到
JSP
页面中。
清单
2
显示了
<fmt:parseDate>
操作的一个示例:
清单
2.
使用
<fmt:parseDate>
标记来解析日期和时间
<c:set var="usDateString">4/1/03 7:03 PM</c:set>
<fmt:parseDate value="${usDateString}" parseLocale="en_US"
type="both" dateStyle="short" timeStyle="short"
var="usDate"/>
<c:set var="gbDateString">4/1/03 19:03</c:set>
<fmt:parseDate value="${gbDateString}" parseLocale="en_GB"
type="both" dateStyle="short" timeStyle="short"
var="gbDate"/>
<ul>
<li> Parsing <c:out value="${usDateString}"/> against the
U.S. English
locale yields a date of <c:out value="${usDate}"/>.</li>
<li> Parsing <c:out value="${gbDateString}"/> against the
British English
locale yields a date of <c:out value="${gbDate}"/>.</li>
</ul>
清单
2
的输出如图
4
所示。
图
4.
清单
2
的输出
由
<fmt:parseDate>
所执行的解析非常严格,注意这一点很重要。正如清单
2
所暗示的那样,要解析的
值必须严格符合特定(特定于语言环境)的样式或模式。这当然更加受限制。另一方面,数据的解析并不
是一个非常适合于表示层的任务。对于生产代码,文本输入的验证和转换最好由后端代码(比如
servlet
)
来处理,而不是通过
JSP
定制标记来处理。
数字标记
就象
<fmt:formatDate>
和
<fmt:parseDate>
标记用于格式化和解析日期一样,
<fmt:formatNumber>
和
<fmt:parseNumber>
标记对数字数据执行类似的功能。
<fmt:formatNumber>
标记用来以特定于语言环境的方式显示数字数据,包括货币和百分数。
<fmt:formatNumber>
操作由语言环境确定,例如,使用句点还是使用逗号来定界数字的整数和小数部分。
下面是它的语法:
<fmt:formatNumber value="
expression
"
type="
type
" pattern="
expression
"
currencyCode="
expression
" currencySymbol="
expression
"
maxIntegerDigits="
expression
" minIntegerDigits="
expression
"
maxFractionDigits="
expression
" minFractionDigits="
expression
"
groupingUsed="
expression
"
var="
name
" scope="
scope
"/>
如
<fmt:formatDate>
的情况一样,只有
value
属性才是必需的。它用来指定将被格式化的数值。
var
和
scope
属性对
<fmt:formatNumber>
操作所起的作用,如它们在
<fmt:formatDate>
中所起的作用
一样。
type
属性的值应当是
number
、
currency
或
percentage
,并指明要对哪种类型的数值进行格式化。
该属性的缺省值是
number
。
pattern
属性优先于
type
属性,允许对遵循
java.text.DecimalFormat
类模式约定的数值进行更精确的格式化。
当
type
属性的值为
currency
时,
currencyCode
属性可以用来显式地指定所显示的数值的货币单位。
与语言和国家或地区代码一样,货币代码也是由
ISO
标准管理的(请参阅
参考资料
以获取所有有效的
ISO
货币符号代码的链接)。该代码用来确定作为已格式化值的一部分显示的货币符号。
另外,您可以使用
currencySymbol
属性来显式地指定货币符号。请注意,由于
JDK 1.4
和相关的
java.util.Currency
类的引入,
<fmt:formatNumber>
操作的
currencyCode
属性优先权超过
currencySymbol
属性。但是对于较老版本的
JDK
而言,
currencySymbol
属性具有优先权。
maxIntegerDigits
、
minIntegerDigits
、
maxFractionDigits
和
minFractionDigits
属性用来
控制小数点前后所显示的有效数字的个数。这些属性要求是整数值。
groupingUsed
属性带有布尔值并控制是否要对小数点前面的数字分组。例如,在英语语言环境中,将较
大数的每三个数字分为一组,每组用逗号定界。其它语言环境用句点或空格来定界这样的分组。该属性的
缺省值为
true
。
清单
3
显示了一个简单的货币示例,它本身是
清单
1
的扩展。在本例中,不指定
currencyCode
或
currencySymbol
属性。而货币是由语言环境设置确定的。
清单
3.
使用
<fmt:formatNumber>
标记显示货币值
<table>
<fmt:timeZone value="US/Eastern">
<c:forEach items="${entryList}" var="blogEntry"
varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<fmt:formatDate value=
"${blogEntry.created}" dateStyle="full"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
<font class="blogPosted">
[My <fmt:formatNumber value="0.02" type="currency"/>
posted at <fmt:formatDate value="${blogEntry.created}"
pattern="h:mm a zz"/>]
</font>
</td></tr>
</c:forEach>
</fmt:timeZone>
</table>
en_US
语言环境的输出如图
5
所示:
图
5.
清单
3
的
en_US
语言环境的输出
fr_CA
语言环境的输出如图
6
所示:
图
6.
清单
3
的
fr_CA
语言环境的输出
如下所示,
<fmt:parseNumber>
操作解析了一个数值,该数值是通过
value
属性或该操作的标记体内
容以特定于语言环境的方式提供的,将结果作为
java.lang.Number
类的实例返回。
type
和
pattern
属性对
<fmt:parseNumber>
和对
<fmt:formatNumber>
起一样的作用。同样,
parseLocale
、
var
和
scope
属性对
<fmt:parseNumber>
起与
<fmt:parseDate>
一样的作用。
<fmt:parseNumber value="
expression
"
type="
type
" pattern="
expression
"
parseLocale="
expression
"
integerOnly="
expression
"
var="
name
" scope="
scope
"/>
<fmt:parseNumber
type="
type
" pattern="
expression
"
parseLocale="
expression
"
integerOnly="
expression
"
var="
name
" scope="
scope
">
body content
</fmt:parseNumber>
先前有关
<fmt:parseDate>
的说明同样适用于
<fmt:parseNumber>
:解析数据并不是一项非常适合于
表示层的任务。如果解析和验证数据作为应用程序业务逻辑的一部分实现,那么软件维护将会得到简化。
由于这个原因,通常建议大家在产品
JSP
页面中避免同时使用
<fmt:parseDate>
和
<fmt:parseNumber>
。
只有
integerOnly
属性才是
<fmt:parseNumber>
所独有的。该属性获取一个布尔值,指出是否应当只
解析所给值的整数部分。如果该属性的值为
true
,那么就忽略要被解析的字符串中跟在小数点后面的任
何数字。该属性的缺省值为
false
。
消息标记
在
JSTL
中用
<fmt:message>
标记实现文本的本地化。该标记允许您从特定于语言环境的资源束中检索
文本消息并显示在
JSP
页面上。而且,由于该操作利用
java.text.MessageFormat
类所提供的功能,
所以可以将参数化的值替换进这样的文本消息,以便动态地定制本地化内容。
用于存储特定于语言环境消息的资源束采用类或特性文件的形式,这些类或特性文件符合标准命名约定,
在这种命名约定中基名和语言环境名组合在一起。例如,请研究名为
Greeting.properties
的特性文件,
它驻留在我们的
weblog
应用程序的类路径中,该类路径位于与
com.taglib.weblog
包相对应的子目录
中。您可以通过在同一目录下指定两个新的特性文件,从而将该特性文件所描述的资源束本地化为英语和
法语,通过追加相应的语言代码来命名。具体而言,这两个文件应当分别命名为
Greeting_en.properties
和
Greeting_fr.properties
。如果希望另一个本地化为加拿大法语,您可以引入第三个特性文件,在
其名称中包含了相应的国家或地区代码(比如
Greeting_fr_CA.properties
)。
这些文件都可以定义相同的特性,但是应当将这些特性的值定制成对应的语言或方言。这种方法如清单
4
和清单
5
所示,它们给出了
Greeting_en.properties
和
Greeting_fr.properties
文件的样本内容。
在这些示例中,定义了两个已本地化的消息。它们可以通过
com.taglib.weblog.Greeting.greeting
和
com.taglib.weblog.Greeting.return
键识别。但是已经将与这些键相关联的值本地化为文件名中所确
定的语言。请注意,出现在
com.taglib.weblog.Greeting.greeting
消息的两个值中的
{0}
模式使已
参数化的值能够在内容生成期间动态地插入到消息中。
清单
4. Greeting_en.properties
本地化资源束的内容
com.taglib.weblog.Greeting.greeting=Hello {0}, and welcome to the JSTL Blog.
com.taglib.weblog.Greeting.return=Return
清单
5. Greeting_fr.properties
本地化资源束的内容
com.taglib.weblog.Greeting.greeting=Bonjour {0}, et bienvenue au JSTL Blog.
com.taglib.weblog.Greeting.return=Retournez
用
JSTL
显示这样的本地化内容,第一步就是指定资源束。
fmt
库为完成这一任务提供了两个定制标记:
<fmt:setBundle>
和
<fmt:bundle>
,它们的行为和前面介绍的
<fmt:setTimeZone>
和
<fmt:timeZone>
标记相似。
<fmt:setBundle>
操作设置了一个缺省资源束,供
<fmt:message>
标记
在特定作用域内使用,而
<fmt:bundle>
指定了为嵌套在其标记体内容中的全部和任意
<fmt:message>
操作所用的资源束。
下面的代码片段显示了
<fmt:setBundle>
标记的语法。
basename
属性是必需的,它标识了设为缺省值
的资源束。请注意,
basename
属性的值不应当包含任何本地化后缀或文件扩展名。清单
4
和清单
5
中
给出的示例资源束的基名为
com.taglib.weblog.Greeting
。
<fmt:setBundle basename="
expression
"
var="
name
" scope="
scope
"/>
可选的
scope
属性指明缺省资源束设置所应用的
JSP
作用域。如果没有显式地指定该属性,就假定为
page
作用域。
如果指定了可选的
var
属性,那么将把由
basename
属性所标识的资源束赋给该属性值所命名的变量。
在这种情况下,
scope
属性指定变量的作用域;没有将缺省资源束赋给相应的
JSP
作用域。
您使用
<fmt:bundle>
标记(其语法如下所示)在其标记体内容的作用域内设置缺省资源束。和
<fmt:setBundle>
一样,只有
basename
属性才是必需的。您可以使用可选的
prefix
属性来为任何嵌
套的
<fmt:message>
操作的
key
值指定缺省前缀。
<fmt:bundle basename="
expression
"
prefix="
expression
">
body content
</fmt:bundle>
一旦设置了资源束,真正起到显示本地化消息作用的是
<fmt:message>
标记。该操作支持两种不同的语
法,这取决于是否需要任何嵌套的
<fmt:param>
标记:
<fmt:message key="
expression
" bundle="
expression
"
var="
name
" scope="
scope
"/>
<fmt:message key="
expression
" bundle="
expression
"
var="
name
" scope="
scope
">
<fmt:param value="
expression
"/>
...
</fmt:message>
对于
<fmt:message>
,只有
key
属性才是必需的。
key
属性的值用来确定要显示在资源束中定义的哪
些消息。
您可以使用
bundle
属性来指定一个显式的资源束,用来查找由
key
属性标识的消息。请注意,该属性
的值必须是实际的资源束,比如当指定
<fmt:setBundle>
操作的
var
属性时由该操作所赋予的资源束。
<fmt:message>
的
bundle
属性不支持字符串值(比如
<fmt:bundle>
和
<fmt:setBundle>
的
basename
属性)。
如果指定了
<fmt:message>
的
var
属性,那么将由该标记所生成的文本消息赋给指定的变量,而不是写
到
JSP
页面。通常,可选的
scope
属性用来指定由
var
属性指定的变量的作用域。
需要的时候您可以通过使用
<fmt:param>
标记的
value
属性来提供文本消息的参数化值。或者,可以将
该值指定为
<fmt:param>
标记体内容,在这种情况下省略该属性。无论参数化值模式出现在消息文本中
的什么地方,由
<fmt:param>
标记指定的值都将合并到从资源束检索的消息,这与
java.text.MessageFormat
类的行为一致。因为参数化值可以通过其下标进行标识,因此嵌套的
<fmt:param>
标记的顺序很重要。
<fmt:bundle>
、
<fmt:message>
和
<fmt:param>
标记的交互作用如清单
6
所示。此处,
<fmt:bundle>
标记通过两个嵌套的
<fmt:message>
标记指定了要在其中检索本地化消息的资源束。这
两个
<fmt:message>
标记的第一个对应于带有一个参数化值的消息,还出现了对应的用于该值的
<fmt:param>
标记。
清单
6.
使用
<fmt:message>
标记显示本地化消息
<fmt:bundle basename="com.taglib.weblog.Greeting">
<fmt:message key="com.taglib.weblog.Greeting.greeting">
<fmt:param value="${user.fullName}"/>
</fmt:message>
<br>
<br>
<center>
<a href=
"<c:url value='/index.jsp'/>"><fmt:message
key="com.taglib.weblog.Greeting.return"/></a>
</center>
</fmt:bundle>
清单
7
演示了
<fmt:bundle>
的
prefix
属性的用法;为
prefix
属性提供的值在嵌套的
<fmt:message>
操作中自动地预先添加到所有
key
值上。因此清单
7
相当于清单
6
,只是清单
7
利用
了这一便利的特性,使得能够在两个
<fmt:message>
标记中使用缩略的
key
值。
清单
7. <fmt:bundle>
的
prefix
属性对
<fmt:message>
标记的影响
<fmt:bundle basename="com.taglib.weblog.Greeting"
prefix="com.taglib.weblog.Greeting.">
<fmt:message key="greeting">
<fmt:param value="${user.fullName}"/>
</fmt:message>
<br>
<br>
<center>
<a href="<c:url value='/index.jsp'/>"><fmt:message key="return"/></a>
</center>
</fmt:bundle>
图
7
和图
8
演示了正在工作的
fmt
库与消息相关的标记,显示了由清单
7
中代码所产生的输出,以及
清单
4
和
清单
5
中的本地化资源束。图
7
显示了当浏览器首选项为英语语言环境时的结果。
图
7.
清单
7
中
en_US
语言环境的输出
图
8
显示了指定法语的语言环境的输出。
图
8.
清单
7
的
fr_CA
语言环境的输出
回页首
结束语
JSTL
fmt
库的定制标记为
JSP
开发人员提供了一种对
Java
平台的国际化
API
的简单访问。文本消
息、数值和日期都可以用对语言环境敏感的方式进行显示,还可以将时间调整到特定的时区。可以从用户
的浏览器设置自动确定特定用户的语言环境,或者由页面作者显式指定特定用户的语言环境。最后,除了
提供用于生成和显示格式化数据的操作之外,
fmt
库还包含了用于解析面向数字和时间数据的定制标记。
参考资料
您可以参阅本文在
developerWorks
全球站点上的
英文原文
.
下载
Weblog
示例应用程序的
源代码
。
Sun
的
JSP
标准标记库
产品页
是学习有关
JSTL
的更多内容的良好起点。
JSTL 1.0
规范
是有关
EL
和四个
JSTL
标记库的最终权威文本。
Jakarta Taglibs
项目是
JSTL 1.0
参考实现的起源。
Shawn Bayern
所著的
JSTL in Action
(
Manning
,
2002
)精彩地概述了所有的
JSTL
特
性,作者是参考实现的主管。
广受欢迎的
Java
编程作者
David Geary
也撰写了有关
JSTL
的书籍,书名为
Core JSTL
。
JSPTags.com
是
JSP
参考资料的目录,它尤其着重定制标记库方面的内容。
Sun
的
Java Web Services Tutorial
中概述了
JSTL
。
Mark Davis
和
Helena Shih
在
“
The Java International API: Beyond JDK 1.1
”
(
developerWorks
,
1998
年
10
月)中广泛地概述了
Java
平台的国际化特性。
如果您对国际化感兴趣,您可能不希望错过
Joe Sam Shirah
的综合性教程:
java
国际化基
础知识
(
developerWorks
,
2002
年
4
月)。
ISO 639
规范
定义了有效的语言代码,而
ISO 3166
规范
定义了有效的国家或地区代码。
与此类似,
ISO 4217
规范
定义了货币代码。
“
VisualAge for Java
版本
3.5.3/WebSphere Studio 3.5.2 ― JSP 1.1
开发
”
(
WebSphere
开发者园地
)是
WBOnline
实战篇,它演示了
servlet
、
JSP
页面和定制标记库
的用法。
通过
Jeff Wilson
的优秀论文
“
使用定制标记控制
JSP
页面
”
(
developerWorks
,
2002
年
1
月)全面了解定制标记库。
Noel Bergman
的文章
“
JSP
标记库:着意设计的更好的可用性
”
(
developerWorks
,
2001
年
12
月)向您说明了声明性标记如何有助于提高
JSP
页面的可用性。
请在
developerWorks
Java
技术专区
查找大量的
Java
技术参考资料。
Web
应用程序的模板式(
stereotypical
)架构分为三层:处理请求的
Web
服务器、实施业务逻辑的应用
程序服务器以及管理永久性数据的数据库。应用程序和数据库层之间的联接通常采用关系数据库中的
SQL
调用格式。当业务逻辑被写入到
Java
语言中时,
JDBC
用于实现这些调用。
如果应用程序调用与其它服务器
(
本地或远程
)
的集成,我们将需要用于在不同子系统之间交换数据的更深
层次的机制。在
Web
应用程序内部和应用程序之间传送数据采用的越来越普遍的一种方法是
XML
文件的
交换。
迄今为止,在我们的
JSTL
之旅中,我们讨论了
JSTL
表达式语言(
expression language
,
EL
)和
core
和
fmt
标记库。在最后一部分,我们将考虑
sql
和
xml
库
--
正如它们的名字表示的一样
--
提供定制标记
来接入和管理从
SQL
数据库和
XML
文件检索到的数据。
xml
库
根据设计,
XML
提供灵活的方式来表示结构化数据,这些数据
同时准备进行验证,因此它尤其适应于在松散联合的系统之间
交换数据。这反过来使其成为
Web
应用程序极具吸引力的集成
技术。
与使用
XML
表示的数据进行交互的第一步是把数据作为一个
XML
文件,对其进行检索并进行分解,以创
建数据结构来接入该文件中的内容。在分解文件后,您可以有选择的对其进行转换以创建新的
XML
文件,
不要遗漏本系列的其它部分
第
1
部分
,“
表达式语言
” (2003
年
2
月
)
第
2
部分,
“
探讨核心
”(2003
年
3
月
)
第
3
部分,
“
表示就是一切
(
Presentation is everything
)
”
(2003
年
4
月
)
您可以对新的
XML
文件进行相同的操作。最终,文件中的数据可以被提取,然后显示或使用作为输入数据
来运行其它操作。
这些步骤都在用于控制
XML
的
JSTL
标记中反映出。根据我们在第
2
部分
探讨核心
中所讨论的,我们使
用
core
库中的
<c:import>
标记来检索
XML
文件。然后使用
<x:parse>
标记来分解该文件,支持标准的
XML
分解技术,如文件对象模式(
Document Object Model
,
DOM)
和简单
XML API
(
Simple API for
XML
,
SAX)
。
<x:transform>
标记可用于转换
XML
文件并依赖标准技术来转换
XML
数据:扩展样式表
语言(
Extensible Stylesheet Language
,
XSL)
。最后,我们提供多个标记来接入和控制分解后的
XML
数据,但是所有这一切都依赖于另一种标准
-
XML
路径语言(
XML Path Language
,
XPath)
,以引用分
解后的
XML
文件中的内容。
分解
XML
<x:parse>
标记有多种格式,取决于用户希望的分解类型。这一项操作最基本的格式使用以下语法:
<x:parse xml="
expression
" var="
name
" scope="
scope
"
filter="
expression
" systemId="
expression
"/>
在这五种属性中,只有
xml
属性是需要的,其值应该是包含要分解的
XML
文件的字符串,或者是
java.io.Reader
实例,通过它可以读取要被分解的文件。此外,您可以使用以下语法,根据
<x:parse>
标记的主体内容来规定要被分解的文件:
<x:parse var="
name
" scope="
scope
"
filter="
expression
" systemId="
expression
">
body content
</x:parse>
var
和
scope
属性规定存储分解后的文件的
scoped
变量。然后
xml
库中的其它标记可以使用这一变量来
运行其它操作。注意,当
var
和
scope
属性存在时,
JSTL
用于表示分解后的文件的数据结构类型以实施
为导向,从而厂商可以对其进行优化。
如果应用程序需要对
JSTL
提供的分解后的文件进行处理,它可以使用另一种格式的
<x:parse>
,它要求
分解后的文件坚持使用一个标准接口。在这种情况下,该标记的语法如下:
<x:parse xml="
expression
" varDom="
name
" scopeDom="
scope
"
filter="
expression
" systemId="
expression
"/>
当您使用
<x:parse>
的这一版本时,表示分解后的
XML
文件的对象必须使用
org.w3c.dom.Document
接口。当根据
<x:parse>
中的主体内容来规定
XML
文件时,您还可以使用
varDom
和
scopeDom
属性
来代替
var
和
scope
属性,语法如下:
<x:parse varDom="
name
" scopeDom="
scope
"
filter="
expression
" systemId="
expression
">
body content
</x:parse>
其它两个属性
filter
和
systemId
可以实现对分解流程的精确控制。
filter
属性规定
org.xml.sax.XMLFilter
类的一个实例,以在分解之前对文件进行过滤。如果要被分解的文件非常大,但
目前的工作只需要处理一小部分内容时这一属性尤其有用。
systemId
属性表示要被分解的文件的
URI
并
解析文件中出现的任何相关的路径。当被分解的
XML
文件使用相关的
URL
来引用分解流程中需要接入的
其它文件或资源时需要这种属性
清单
1
展示了
<x:parse>
标记的使用,包括与
<c:import>
的交互。此处
<c:import>
标记用于检索
众所周知的
Slashdot Web
网站的
RDF Site Summary (RSS)
反馈,然后使用
<x:parse>
分解表示
RSS
反馈的
XML
文件,表示分解后的文件的以实施为导向的数据结构被保存到名为
rss
的变量(带有
page
范围)中。
清单
1
:
<x:parse>
与
<c:import>
的交互
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
转换
XML
XML
通过
XSL
样式表来转换。
JSTL
使用
<x:transform>
标记来支持这一操作。与
<x:parse>
的情况一
样,
<x:transform>
标记支持多种不同的格式。
<x:transform>
最基本的格式的语法是:
<x:transform xml="
expression
" xslt="
expression
"
var="
name
" scope="
scope
"
xmlSystemId="
expression
" xsltSystemId="
expression
">
<x:param name="
expression
" value="
expression
"/>
...
</x:transform>
此处,
xml
属性规定要被转换的文件,
xslt
属性规定定义这次
转换的样式表。这两种属性是必要的,其它属性为可选。
与
<x:parse>
的
xml
属性一样,
<x:transform>
的
xml
属
性值可以是包含
XML
文件的字符串,或者是接入这类文件的
Reader
。此外,它还可以采用
org.w3c.dom.Document
类
或
javax.xml.transform.Source
类的实例格式。最后,它
还可以是使用
<x:parse>
操作的
var
或
varDom
属性分配的
变量值。
而且,您可以根据
<x:transform>
操作的主体内容来包含要
被转换的
XML
文件。在这种情况下,
<x:transform>
的语法是:
<x:transform xslt="
expression
"
var="
name
" scope="
scope
"
xmlSystemId="
expression
" xsltSystemId="
expression
">
body content
<x:param name="
expression
" value="
expression
"/>
...
</x:transform>
关于
RSS
RDF Site Summary (RSS)
是许多
以新闻为导向的网站公布的
XML
文件
格式,它列出它们当前的标题,提供链
接到相关文章的
URL
。同样,它提供
在
Web
上联合新闻的简单机制。关于
RSS
的更详细信息,请参阅
参考资
料
。
在这两种情况下,规定
XSL
样式表的
xslt
属性应是字符串、
Reader
或
javax.xml.transform.Source
实例。
如果
var
属性存在,转换后的
XML
文件将分配给相应的
scoped
变量,作为
org.w3c.dom.Document
类的一个实例。通常,
scope
属性规定这类变量分配的范围。
<x:transform>
标记还支持将转换结果存储到
javax.xml.transform.Result
类的一个实例中,而不是
作为
org.w3c.dom.Document
的一个实例。如果
var
和
scope
属性被省略,
result
对象规定作为
result
属性的值,
<x:transform>
标记将使用该对象来保存应用该样式表的结果。清单
2
中介绍了使用
<x:transform>
的
result
属性的这两种语法的变化:
清单
2
:使用
result
属性来提供
javax.xml.transform.Result
实例时,
<x:transform>
操作的
语法变化
<x:transform xml="
expression
" xslt="
expression
"
result="
expression
"
xmlSystemId="
expression
" xsltSystemId="
expression
">
<x:param name="
expression
" value="
expression
"/>
...
</x:transform>
<x:transform xslt="
expression
"
result="
expression
"
xmlSystemId="
expression
" xsltSystemId="
expression
">
body content
<x:param name="
expression
" value="
expression
"/>
...
</x:transform>
无论您采用这两种
<x:transform>
格式中的那一种,您都必须从定制标记单独创建
javax.xml.transform.Result
对象。该对象自身作为
result
属性的值提供。
如果既不存在
var
属性,也不存在
result
属性,转换的结果将简单地插入到
JSP
页面,作为处理
<x:transform>
操作的结果。当样式表用于将数据从
XML
转换成
HTML
时尤其有用,如清单
3
所示:
清单
3
:在
JSP
页面直接显示转换的
XML
数据
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<c:import var="rssToHtml" url="/WEB-INF/xslt/rss2html.xsl"/>
<x:transform xml="${rssFeed}" xslt="${rssToHtml}"/>
在本例中,使用
<c:import>
标记来读取
RSS
反馈和适当的样式表。样式表的输出结果是
HTML
,通过
忽略
<x:transform>
的
var
和
result
属性来直接显示。图
1
显示了实例结果:
图
1
:清单
3
的输出结果
与
<x:parse>
的
systemId
属性一样,
<x:transform>
的
xmlSystemId
和
xsltSystemId
属性用于
解析
XML
文件中相关的路径。在这种情况下,
xmlSystemId
属性应用于根据标记的
xml
属性值提供的
文件,而
xsltSystemId
属性用于解析根据标记的
xslt
属性规定的样式表中的相关路径。
如果正在推动文件转换的样式表使用了参数,我们使用
<x:param>
标记来规定这些参数。如果参数存在,
那么这些标记必须在
<x:transform>
标记主体内显示。如果根据主体内容规定了要被转换的
XML
文件,
那么它必须先于任何
<x:param>
标记。
<x:param>
标记有两种必要的属性
-- name
和
value --
就象本系列
第
2
部分
和
第
3
部分
中讨论
的
<c:param>
和
<fmt:param>
标记一样。
处理
XML
内容
XML
文件的分解和转换操作都是基于整个文件来进行。但是,在您将文件转换成一种可用的格式之后,一
项应用程序通常只对文件中包含的一部分数据感兴趣。鉴于这一原因,
xml
库包括多个标记来接入和控制
XML
文件内容的各个部分。
如果您已经阅读了本系列第
2
部分
(
探讨核心
)
,您将对这些
xml
标记的名字非常熟悉。它们都基于
JSTL
core
库相应的标记。但是,这些
core
库标记使用
EL
表达式,通过它们的
value
属性来接入
JSP
容器
中的数据,而它们在
xml
库中的副本使用
XPath
表达式,通过
select
属性接入
XML
文件中的数据。
XPath
是引用
XML
文件中元素及它们的属性值和主体内容的标准化符号。正如其名字代表的一样,这种
符号与文件系统路径的表示方法类似,使用短斜线来分开
XPath
语句的组分。这些组分对映于
XML
文件
的节点,连续的组分匹配嵌套的
Element
。此外,星号可以用于作为通配符来匹配多个节点,括号内的表
达式可以用于匹配属性值和规定索引。有多种在线参考资料介绍
XPath
和它的使用
(
见
参考资料
)
。
要显示
XML
文件的数据的一个
Element
,使用
<x:out>
操作,它与
core
库的
<c:out>
标记类似。 但
是,
<c:out>
使用名为
value
和
escapeXml
的属性,
<x:out>
的属性为
select
和
escapeXml
:
<x:out select="
XPathExpression
" escapeXml="
boolean
"/>
当然,两者的区别在于
<x:out>
的
select
属性值必须是
XPath
表达式,而
<c:out>
的
value
属性必
须是
EL
表达式。两种标记的
escapeXml
属性的意义是相同的。
清单
4
显示了
<x:out>
操作的使用。注意,规定用于
select
属性的
XPath
表达式由一个
EL
表达式规
定为
scoped
变量,尤其是
$rss
。这一
EL
表达式根据将被求值的
XPath
语句来识别分解后的
XML
文件。
该语句在此处查找名为
title
且父节点名为
channel
的
Element
,从而选择它找到的第一个
Element(
根
据表达式尾部
[1]
索引规定
)
。这一
<x:out>
操作的结果是显示这一
Element
的主体内容,关闭正在转义
(
Escaping
)的
XML
字符。
清单
4
:使用
<x:out>
操作来显示
XML Element
的主体内容
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
<x:out select="$rss//*[name()='channel']/*[name()='title'][1]"
escapeXml="false"/>
除了
<x:out>
之外,
JSTL xml
库包括以下控制
XML
数据的标记:
<x:set>
,向
JSTL scoped
变量分配
XPath
表达式的值
<x:if>
,根据
XPath
表达式的布尔值来条件化内容
<x:choose>
、
<x:when>
和
<x:otherwise>
,根据
XPath
表达式来实施互斥的条件化
<x:forEach>
,迭代根据
XPath
表达式匹配的多个
Elements
每个这些标记的操作与
core
库中相应的标记类似。例如,清单
5
中显示的
<x:forEach>
的使用,
<x:forEach>
操作用于迭代
XML
文件中表示
RSS
反馈的所有名为
item
的
Element
。注意,
<x:forEach>
主体内容中嵌套的两个
<x:out>
操作中的
XPath
表达式与
<x:forEach>
标记正在迭代的
节点相关。它们用于检索每个
item element
的子节点
link
和
title
。
清单
5
:使用
<x:out>
和
<x:forEach>
操作来选择和显示
XML
数据
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
<a href="<x:out select="$rss//*[name()='channel']/*[name()='link'][1]"/>"
><x:out select="$rss//*[name()='channel']/*[name()='title'][1]"
escapeXml="false"/></a>
<x:forEach select="$rss//*[name()='item']">
<li> <a href="<x:out select="./*[name()='link']"/>"
><x:out select="./*[name()='title']" escapeXml="false"/></a>
</x:forEach>
清单
5
中
JSP
程序代码的输出结果与
清单
3
类似,它在
图
1
中显示。
xml
库以
XPath
为导向的标记提
供备选的样式表来转换
XML
内容,尤其是当最后的输出结果是
HTML
的情况。
回页首
sql
库
JSTL
第
4
个也是最后一个操作是
sql
定制标记库。正如其名字代表的一样,该库提供与关系数据库交互的
标记。尤其是
sql
库定义规定数据源、发布查询和更新以及将查询和更新编组到事务处理中的标记。
Datasource
Datasource
是获得数据库连接的工厂。它们经常实施某些格式的连接库来最大限度地降低与创建和初始
化连接相关的开销。
Java 2 Enterprise Edition (J2EE)
应用程序服务器通常内置了
Datasource
支持,
通过
Java
命名和目录接口(
Java Naming and Directory Interface
,
JNDI)
它可用于
J2EE
应用程
序。
JSTL
的
sql
标记依赖于
Datasource
来获得连接。实际上包括可选的
dataSource
属性以明确规定它们
的连接工厂作为
javax.sql.DataSource
接口实例,或作为
JNDI
名。
您可以使用
<sql:setDataSource>
标记来获得
javax.sql.DataSource
实例,它采用以下两种格式:
<sql:setDataSource dataSource="
expression
"
var="
name
" scope="
scope
"/>
<sql:setDataSource url="
expression
" driver="
expression
"
user="
expression
" password="
expression
"
var="
name
" scope="
scope
"/>
第一种格式只需要
dataSource
属性,而第二种格式只需要
url
属性。
通过提供
JNDI
名作为
dataSource
属性值,您可以使用第一种格式来接入与
JNDI
名相关的
datasource
。
第二种格式将创建新的
datasource
,使用作为
url
属性值提供的
JDBC URL
。可选的
driver
属性规定实
施数据库
driver
的类的名称,同时需要时
user
和
password
属性提供接入数据库的登录证书。
对于
<sql:setDataSource>
的任何一种格式而言,可选的
var
和
scope
属性向
scoped
变量分配特定
的
datasource
。如果
var
属性不存在,那么
<sql:setDataSource>
操作设置供
sql
标记使用的缺省
datasource
,它们没有规定明确的
datasource
。
您还可以使用
javax.servlet.jsp.jstl.sql.dataSource
参数来配置
sql
库的缺省
datasource
。在实践
中,在应用程序的
Web.xml
文件中添加与清单
6
中显示的类似的程序代码是规定缺省
datasource
最方
便的方式。使用
<sql:setDataSource>
来完成这一操作要求使用
JSP
页面来初始化该应用程序,因此可
以以某种方式自动运行这一页面。
清单
6
:使用
JNDI
名来设置
JSTL
在
web.xml
部署描述符中的缺省
datasource
<context-param>
<param-name>javax.servlet.jsp.jstl.sql.dataSource</param-name>
<param-value>jdbc/blog</param-value>
</context-param>
提交查询和更新
在建立了
datasource
接入之后,您可以使用
<sql:query>
操作来执行查询,同时使用
<sql:update>
操
作来执行数据库更新。查询和更新使用
SQL
语句来规定,它可以是使用基于
JDBC
的
java.sql.PreparedStatement
接口的方法来实现参数化。参数值使用嵌套的
<sql:param>
和
<sql:dateParam>
标记来规定。
支持以下三种
<sql:query>
操作:
<sql:query sql="
expression
" dataSource="
expression
"
var="
name
" scope="
scope
"
maxRows="
expression
" startRow="
expression
"/>
<sql:query sql="
expression
" dataSource="
expression
"
var="
name
" scope="
scope
"
maxRows="
expression
" startRow="
expression
">
<sql:param value="
expression
"/>
...
</sql:query>
<sql:query dataSource="
expression
"
var="
name
" scope="
scope
"
maxRows="
expression
" startRow="
expression
">
SQL statement
<sql:param value="
expression
"/>
...
</sql:query>
前两种格式只要求
sql
和
var
属性,第三种格式只要求
var
属性。
var
和
scope
属性规定存储查询结果的
scoped
变量。
maxRows
属性可以用于限制查询返回的行数,
startRow
属性允许忽略一些最开始的行数
(
如当结果集(
Result set
)由数据库来构建时忽略
)
。
在执行了查询之后,结果集被分配给
scoped
变量,作为
javax.servlet.jsp.jstl.sql.Result
接口的一个
实例。这一对象提供接入行、列名称和查询的结果集大小的属性,如表
1
所示:
表
1
:
javax.servlet.jsp.jstl.sql.Result
接口定义的属性
属性
说明
rows
一排
SortedMap
对象,每个对象对映列名和结果集中的单行
rowsByIndex
一排数组,每个对应于结果集中的单行
columnNames
一排对结果集中的列命名的字符串,采用与
rowsByIndex
属性相同的顺序
rowCount
查询结果中总行数
limitedByMaxRows
如果查询受限于
maxRows
属性值为真
在这些属性中,
rows
尤其方便,因为您可以使用它来在整个结果集中进行迭代和根据名字访问列数据。
我们在
清单
7
中阐述了这一操作,查询的结果被分配到名为
queryResults
的
scoped
变量中,然后使用
core
库中的
<c:forEach>
标记来迭代那些行。嵌套的
<c:out>
标记利用
EL
内置的
Map
收集支持来查
找与列名称相对应的行数据。
(
记得在
第
1
部分
,
${row.title}
和
${row["title"]}
是相等的表达式。
)
清单
7
还展示了使用
<sql:setDataSource>
来关联
datasource
和
scoped
变量,它由
<sql:query>
操作通过其
dataSource
属性随后接入。
清单
7
:使用
<sql:query>
来查询数据库,使用
<c:forEach>
来迭代整个结果集
<sql:setDataSource var="dataSrc"
url="jdbc:mysql:///taglib" driver="org.gjt.mm.mysql.Driver"
user="admin" password="secret"/>
<sql:query var="queryResults" dataSource="${dataSrc}">
select * from blog group by created desc limit ?
<sql:param value="${6}"/></sql:query>
<table border="1">
<tr>
<th>ID</th>
<th>Created</th>
<th>Title</th>
<th>Author</th>
</tr>
<c:forEach var="row" items="${queryResults.rows}">
<tr>
<td><c:out value="${row.id}"/></td>
<td><c:out value="${row.created}"/></td>
<td><c:out value="${row.title}"/></td>
<td><c:out value="${row.author}"/></td>
</tr>
</c:forEach>
</table>
图
2
显示了清单
7
中
JSTL
程序代码的实例页面输出结果。注意:清单
7
中
<sql:query>
操作主体中出现
的
SQL
语句为参数化语句。
图
2
:清单
7
的输出
在
<sql:query>
操作中,
SQL
语句根据主体内容来规定,或者使用?字符,通过
sql
属性实现参数化。
对于
SQL
语句中每个这样的参数来说,应有相应的
<sql:param>
或
<sql:dateParam>
操作嵌套到
<sql:query>
标记的主体中。
<sql:param>
标记只采用一种属性
-- value --
来规定参数值。此外,
当参数值为字符串时,您可以忽略
value
属性并根据
<sql:param>
标记的主体内容来提供参数值。
表示日期、时间或时间戳的参数值使用
<sql:dateParam>
标记来规定,使用以下语法:
<sql:dateParam value="
expression
" type="
type
"/>
对于
<sql:dateParam>
来说,
value
属性的表达式必须求
java.util.Date
类实例的值,同时
type
属
性值必须是
date
、
time
或
timestamp
,由
SQL
语句需要那类与时间相关的值来决定。
与
<sql:query>
一样,
<sql:update>
操作支持三种格式:
<sql:update sql="
expression
" dataSource="
expression
"
var="
name
" scope="
scope
"/>
<sql:update sql="
expression
" dataSource="
expression
"
var="
name
" scope="
scope
">
<sql:param value="
expression
"/>
...
</sql:update>
<sql:update dataSource="
expression
"
var="
name
" scope="
scope
">
SQL statement
<sql:param value="
expression
"/>
...
</sql:update>
sql
和
dataSource
属性有与
<sql:query>
相同的
<sql:update>
语义。同样,
var
和
scope
属性可
以用于规定
scoped
变量,但在这种情况下,分配给
scoped
变量的值将是
java.lang.Integer
的一个实
例,显示作为数据库更新结果而更改的行数。
管理事务处理
事务处理用于保护作为一个组必须成功或失败的一系列数据库操作。事务处理支持已经嵌入到
JSTL
的
sql
库中,通过将相应的
<sql:query>
和
<sql:update>
操作嵌套到
<sql:transaction>
标记的主体内容中,
从而将一系列查询和更新操作打包到一个事务处理中也就显得微不足道了。
<sql:transaction>
语法如下:
<sql:transaction dataSource="
expression
" isolation="
isolationLevel
">
<sql:query .../>
or
<sql:update .../>
...
<sql:transaction>
操作没有必需的属性。如果您忽略了
dataSource
属性,那么使用
JSTL
的缺省
datasource
。
isolation
属性用于规定事务处理的隔离级别,它可以是
read_committed
、
read_uncommitted
、
repeatable_read
或
serializable
。如果您未规定这一属性,事务处理将使用
datasource
的缺省隔离级别。
您可能希望所有嵌套的查询和更新必须使用与事务处理相同的
datasource
。实际上,嵌套到
<sql:transaction>
操作中的
<sql:query>
或
<sql:update>
不能用于规定
dataSource
属性。它将
自动使用与周围的
<sql:transaction>
标记相关的
datasource (
显性或隐性
)
。
清单
8
显示了如何使用
<sql:transaction>
的一个实例:
清单:使用
<sql:transaction>
来将数据库更新联合到事务处理中
<sql:transaction>
<sql:update sql="update blog set title = ? where id = ?">
<sql:param value="New Title"/>
<sql:param value="${23}"/>
</sql:update>
<sql:update sql="update blog set last_modified = now() where id = ?">
<sql:param value="${23}"/>
</sql:update>
</sql:transaction>
回页首
注意事项
JSTL
的
xml
和
sql
库使用定制标记,从而能够在
JSP
页面上实施复杂的功能,但是在您的表示层实施
这类功能可能不是最好的方法。
对于多位开发人员长期编写的大型应用程序来说,实践证明,用户接口、基本的业务逻辑和数据仓库之间
的严格分离能够长期简化软件维护。广泛流行的模式
/
视图
/
控制器(
Model-View-Controller
,
MVC
)
设计模板是这一
“
最佳实践
”
的公式化。在
J2EE Web
应用程序领域中,模式是应用程序的业务逻辑,视图
是包含表示层的
JSP
页面。
(
控制器是
form handlers
和其它服务器端机制,使浏览器操作能够开始更改
模式并随后更新视图。
) MVC
规定应用程序的三个主要
elements--
模式、视图和控制器
--
相互之间有最
小的相关性,从而限制相互之间的交互到统一、精心定义的接口。
应用程序依赖
XML
文件来进行数据交换以及关系数据库来提供数据永久性都是应用程序业务逻辑(也就是
其模式)的特征。因此,使用
MVC
设计模板建议无需在应用程序的表示层(也就是其视图)反映这些实
施细节。如果
JSP
用于实施表示层,那么使用
xml
和
sql
库将违反
MVC
,因为它们的使用将意味着在
表示层内暴露基本业务逻辑的
elements
。
鉴于这一原因,
xml
和
sql
库最适用于小型项目和原型工作。应用程序服务器对
JSP
页面的动态编译也
使得这些库中的定制标记可以用于作为调试工具。
回页首
结束语
在本系列中,我们讨论了
4
个
JSTL
定制标记库的功能及它们的使用。在
第
1
部分
和
第
2
部分
,我们
讨论通过
El
和
core
库标记的使用,您如何在许多常见情况下避免
JSP
脚本程序。
第
3
部分
关注使用
fmt
库来本地化
Web
内容。
在最后一部分,我们讨论了
xml
和
sql
库的功能。如果您愿意接受将业务逻辑包含到表示层的结果,这
两个库中的标记都使其能够非常轻松地将
XML
文件和关系数据库中的内容结合到
JSP
页面。这两个库还
展示了当集成
<sql:query>
和
<c:forEach>
时,
JSTL
库如何构建和集成,以及
xml
库利用
<c:import>
操作的能力。
参考资料
您可以参阅本文在
developerWorks
全球站点上的
英文原文
.
下载
Weblog
实例应用程序的
源代码
。
JSTL Web site
是深入了解
JSTL
不错的起点。
JSTL 1.0 Specification
是
EL
和四个
JSTL
标记库最权威的参考资料。
Jakarta Taglibs
项目是
JSTL 1.0
参考实现的主页。
Shawn Bayern
撰写的
JSTL in Action
Manning
,
2002
年)出色地讨论了由参考实现领导
编写的全部
JSTL
功能。
David Geary
是一位很受欢迎的
Java
编程方面的作者,他还著有一部关于
JSTL
的书籍,名
为
Core JSTL
(Prentice-Hall and Sun Microsystems Press, 2002)
。
JSPTags.com
是一个
JSP
参考资料目录,它尤其着重于定制标记库方面的内容。
Sun
的
Java Web Services Tutorial
的一部分也涉及到了对
JSTL
的讨论。
阅读
James Lewin
的
“
RSS
新闻反馈简介
”(
developerWorks
, 2000
年
11
月
)
以了解更多关
于
RSS
的信息。
Mark Colan
在
“
Putting XSL transformations to work
” (
developerWorks
, 2001
年
10
月
)
提供了
XSL
的介绍性概述。
参阅
Parand Tony Darugar
撰写的
“
Effective XML processing with DOM and XPath in
Java
”(
developerWorks
, 2002
年
5
月
)
以更好地了解
XPath
及其与文件对象模式
(
Document Object Model
,
DOM)
的关系
.
通过动手实践指南
“
使用
JDBC
构建基于
Web
的应用程序
”(
developerWorks
, 2001
年
12
月
)
来全面了解
JDBC
。
通过学习
“
Using JSPs and custom tags within VisualAge for Java and WebSphere
Studio
” (
WebSphere Developer Domain
)
,动手实践
WBOnline
,这篇文章演示了如何使
用
servlet
、
JSP
页面及定制标记库
通过
Jeff Wilson
的优秀文章
“
使用定制标记控制
JSP
页面
” (
developerWorks
,
2002
年
1
月)来学习关于定制标记库的全部内容。
Noel Bergman
的文章
“
JSP
标记库:着意设计的更好的可用性
”
(
developerWorks
,
2001
年
12
月)向您显示了声明性标记是如何帮助改善
JSP
页面的可用性。
在
developerWorks
Java
技术专区
可以找到数百篇有关
Java
技术的参考资料。