jstl表达式知识点

1 篇文章 0 订阅
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 技术的参考资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饭九钦vlog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值