JSP 标准标记库(JSP Standard Tag Library,JSTL)fmt 库通过一组颇受关注的定制标记提供了用于访问所有 Java 编程语言国际化功能的便利方式。Mark Kolb 研究了用于对数据进行格式化和国际化的 fmt 标记。
在本系列的前几篇文章中,我们讨论了 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 中,影响数据本地化方式的因素主要有两个。一个是用户的 语言环境,另一个是用户的 时区。语言环境表示某一特定区域或文化的语言习惯,包括日期、数字和货币金额的格式。一个语言环境始终会有一种相关联的语言,在许多情况下这种语言是由多个语言环境共享的某种语言的方言。例如,美国英语、英国英语、澳大利亚英语和加拿大英语都具有不同的英语语言环境,而法国、比利时、瑞士和加拿大所用的法语方言则都具有不同的法语语言环境。
别错过了本系列的其它文章 第 2 部分,“ JSTL 入门:探讨 core”(2003 年 3 月) |
时区是数据本地化中的第二个因素,这仅仅是因为一些语言环境分布的地理区域很广。当您显示有关跨洲语言环境(比如澳大利亚英语)的时间信息时,针对用户时区定制数据与对其进行正确格式化一样重要。
但是这就有了一个问题:应用程序如何确定用户的语言环境和时区?在 Java 应用程序的情况下,JVM 能够通过与本地操作系统进行交互来设置缺省语言环境和时区。虽然这种方法对于桌面应用程序而言可以正常工作,但是它实际上并不适合于服务器端的 Java 应用程序,因为这种应用程序所处理的请求,可能来自于距离该应用程序所驻留的服务器万里之遥的地方。
幸运的是,HTTP 协议通过 Accept-Language
请求头将本地化信息从浏览器传递至服务器。许多 Web 浏览器允许用户定制他们的语言首选项,如图 1 所示。通常,那些没有为一种或多种首选语言环境提供显式设置的浏览器会询问操作系统以确定在 Accept-Language
头中发送哪个值(或哪些值)。servlet 规范通过 javax.servlet.ServletRequest
类的 getLocale()
和 getLocales()
方法自动地利用 HTTP 协议的这一功能。JSTL fmt
库中的定制标记又会利用这些方法来自动地确定用户的语言环境,从而相应地调整它们的输出。
但遗憾的是,不存在将用户的时区从浏览器传输到服务器的标准 HTTP 请求头。因此,那些希望自己的 Web 应用程序对时间数据进行本地化的用户,将需要实现他们自己的机制,用来确定和跟踪特定于用户的时区。例如,在本系列文章第 2 部分“ JSTL 入门:探讨 core”中所介绍的 Weblog 应用程序包含了一种将用户的时区首选项存储在 cookie 中的方式。
fmt 库
JSTL fmt
库中的定制标记主要分成四组。第一组允许您设置本地化上下文,其它标记将在其中进行操作。换句话说,这组标记允许页面作者显式地设置其它 fmt
标记在格式化数据时将要使用的语言环境和时区。第二组和第三组标记分别支持对日期和数字进行格式化和解析。最后一组标记侧重于对文本消息进行本地化。
既然我们已经有了些基本了解,那就让我们集中精力逐个研究这四组标记,并演示其用法。
本地化上下文标记
正如我们已经讨论过的那样,JSTL 标记在格式化数据时所使用的语言环境往往是通过查看用户浏览器发送的每个 HTTP 请求所包含的 Accept-Language
头来确定的。如果没有提供这样的头,那么 JSTL 提供一组 JSP 配置变量,您可以设置这些变量以指定缺省的语言环境。如果尚未设置这些配置变量,那么就使用 JVM 的缺省语言环境,该缺省语言环境是从 JSP 容器所运行的操作系统中获取的。
fmt
库提供了其自身的定制标记,以覆盖这个确定用户语言环境的过程: <fmt:setLocale>
。正如下面的代码片段所示, <fmt:setLocale>
操作支持三个属性:
|
其中只有一个属性是必需的: 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>
标记来显式指定用户会话的语言环境设置:
|
JSP 容器处理完该 JSP 代码段之后,将忽略用户浏览器设置中所指定的语言首选项。
<fmt:setTimeZone>
操作像 <fmt:setLocale>
一样,可以用来设置其它 fmt
定制标记所使用的缺省时区值。它的语法如下所示:
|
和 <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>
操作将 TimeZone
实例的值存储在限定了作用域的变量中。在本例中,您可以使用 var
属性来命名限定了作用域的变量,用 scope
属性来指定该变量的作用域(例如,就象这两个属性用在 <c:set>
和 <c:if>
操作中)。请注意,当您以这种方式使用 <fmt:setTimeZone>
操作时,它唯一的副作用就是设置指定的变量。当指定 var
属性时,对于任何其它 JSTL 标记使用什么时区,不会对 JSP 环境作任何更改。
这组中的最后一个标记是 <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>
的语法如下所示:
|
只有 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
值,这样一来所有的日期字段就将用一种特定于语言环境的格式进行显示。
|
在 <c:forEach>
循环体中,第二个 <fmt:formatDate>
操作只用来显示每个项的创建日期的时间部分。在本例中, pattern
属性用来控制时间值的格式化、并控制指定一位数的小时显示(如果可能的话)、12 小时的时钟和缩写时区的输出。输出如图 2 所示:
更准确地说,用户浏览器设置指定首选项是英语时,就会产生图 2 中所示的输出。但是由于 <fmt:formatDate>
对用户语言环境敏感,所以浏览器首选项的改变将导致生成不同的内容。例如,当给定的首选项是法语语言环境时,则结果会如图 3 所示:
<fmt:formatDate>
生成了 java.util.Date
实例的本地化字符串表示,而 <fmt:parseDate>
操作执行相反的操作:给定一个表示日期和/或时间的字符串,它将生成相应的 Date
对象。 <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 的输出如图 4 所示。
由 <fmt:parseDate>
所执行的解析非常严格,注意这一点很重要。正如清单 2 所暗示的那样,要解析的值必须严格符合特定(特定于语言环境)的样式或模式。这当然更加受限制。另一方面,数据的解析并不是一个非常适合于表示层的任务。对于生产代码,文本输入的验证和转换最好由后端代码(比如 servlet)来处理,而不是通过 JSP 定制标记来处理。
数字标记
就象 <fmt:formatDate>
和 <fmt:parseDate>
标记用于格式化和解析日期一样, <fmt:formatNumber>
和 <fmt:parseNumber>
标记对数字数据执行类似的功能。
<fmt:formatNumber>
标记用来以特定于语言环境的方式显示数字数据,包括货币和百分数。 <fmt:formatNumber>
操作由语言环境确定,例如,使用句点还是使用逗号来定界数字的整数和小数部分。下面是它的语法:
|
如 <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
属性。而货币是由语言环境设置确定的。
|
en_US
语言环境的输出如图 5 所示:
fr_CA
语言环境的输出如图 6 所示:
如下所示, <fmt:parseNumber>
操作解析了一个数值,该数值是通过 value
属性或该操作的标记体内容以特定于语言环境的方式提供的,将结果作为 java.lang.Number
类的实例返回。 type
和 pattern
属性对 <fmt:parseNumber>
和对 <fmt:formatNumber>
起一样的作用。同样, parseLocale
、 var
和 scope
属性对 <fmt:parseNumber>
起与 <fmt:parseDate>
一样的作用。
|
先前有关 <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}
模式使已参数化的值能够在内容生成期间动态地插入到消息中。
|
清单 5. Greeting_fr.properties 本地化资源束的内容
|
用 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
。
|
可选的 scope
属性指明缺省资源束设置所应用的 JSP 作用域。如果没有显式地指定该属性,就假定为 page
作用域。
如果指定了可选的 var
属性,那么将把由 basename
属性所标识的资源束赋给该属性值所命名的变量。在这种情况下, scope
属性指定变量的作用域;没有将缺省资源束赋给相应的 JSP 作用域。
您使用 <fmt:bundle>
标记(其语法如下所示)在其标记体内容的作用域内设置缺省资源束。和 <fmt:setBundle>
一样,只有 basename
属性才是必需的。您可以使用可选的 prefix
属性来为任何嵌套的 <fmt:message>
操作的 key
值指定缺省前缀。
|
一旦设置了资源束,真正起到显示本地化消息作用的是 <fmt:message>
标记。该操作支持两种不同的语法,这取决于是否需要任何嵌套的 <fmt:param>
标记:
|
对于 <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>
标记。
|
清单 7 演示了 <fmt:bundle>
的 prefix
属性的用法;为 prefix
属性提供的值在嵌套的 <fmt:message>
操作中自动地预先添加到所有 key
值上。因此清单 7 相当于清单 6,只是清单 7 利用了这一便利的特性,使得能够在两个 <fmt:message>
标记中使用缩略的 key
值。
|
图 7 和图 8 演示了正在工作的 fmt
库与消息相关的标记,显示了由清单 7 中代码所产生的输出,以及 清单 4和 清单 5中的本地化资源束。图 7 显示了当浏览器首选项为英语语言环境时的结果。
图 8 显示了指定法语的语言环境的输出。
结束语
JSTL fmt
库的定制标记为 JSP 开发人员提供了一种对 Java 平台的国际化 API 的简单访问。文本消息、数值和日期都可以用对语言环境敏感的方式进行显示,还可以将时间调整到特定的时区。可以从用户的浏览器设置自动确定特定用户的语言环境,或者由页面作者显式指定特定用户的语言环境。最后,除了提供用于生成和显示格式化数据的操作之外, fmt
库还包含了用于解析面向数字和时间数据的定制标记。