声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4289401.html
Servlet事件监听器... 19
监听域对象的创建和销毁... 19
ServletContextListener接口... 19
HttpSessionListener接口... 19
ServletRequestListener接口... 20
监听域对象的属性变化... 20
attributeAdded. 20
attributeRemoved. 20
attributeReplaced. 20
感知Session绑定的事件监听器... 22
HttpSessionBindingListener接口... 22
HttpSessionActivationListener接口... 22
示例... 22
国际化... 23
与国际化相关的Java类... 23
Locale类... 23
DateFormat类... 25
获得DateFormat类实例对象... 25
日期/时间的格式化和解析... 26
示例... 26
NumberFormat类... 28
获得NumberFormat类实例对象... 28
数值的格式化和解析... 28
示例... 28
MessageFormat类... 30
简单示例... 30
在应用程序中动态设置占位符的格式类型和模式... 31
直接在占位符中设置占位符的格式类型和模式... 32
ResourceBundle类... 34
装载资源包与读取资源信息... 34
ResourceBundle类的扩展应用... 35
自定义ResourceBundle的子类... 35
ListResourceBundle类... 37
Web应用的国际化... 38
获取Web应用中的本地信息... 38
Web应用的国际化示例... 38
Servlet事件监听器
按监听的对象划分,Servlet 2.4规范中定义的事件监听器可以分为如下三种类型:
l 用于监听应用程序环境对象(ServletContext)的事件监听器;
l 用于监听用户会话对象(HttpSession)的事件监听器;
l 用于监听请求消息对象(ServletRequest)的事件监听器.
按照监听的事件类型进行划分,Servlet 2.4规范中定义的事件监听器又可以分为)三种类型:
l 用于监听域对象自身的创建和销毁的事件监听器;
l 用于监听域对象中的属性的增加和删除的事件监听器;
l 用于监听绑定到HttpSession域中的某个对象的状态的事件监听器;
web.xml中配置:
<listener>
<listener-class>xx.MyListener</listener-class>
</listener>
监听域对象的创建和销毁
ServletContextListener接口
用于监听ServletContext对象的创建和销毁的事件。
contextInitialized(ServletContextEvent sce)
contextDestroyed(ServletContextEvent sce)
HttpSessionListener接口
用于监听用户会话对象HttpSession的创建和销毁事件。
sessionCreated(HttpSessionEvent se)
sessionDestroyed(HttpSessionEvent se)
ServletRequestListener接口
用于监听ServletRequest对象的创建和销毁事件。
requestInitialized(ServletRequestEvent sre)
requestDestroyed(ServletRequestEvent sre)
监听域对象的属性变化
这些监听器接口分别是:用于监听ServletContext对象中的属性变更信息的ServletContextAttributeListener接口,用于监听HttpSession对象中的属性变更信息的HttpSessionAttributeListener接口,用于监听ServletRequest对象中的属性变更信息的ServletRequestAttributeListener接口。这三个接口中都定义了三个方法来处理被监听对象中的属性的增加、删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同,它们的参数与发生属性更改的域对象相对应。
attributeAdded
向域对象中增加一个属性时调用。
ServletContextAttributeListener.attributeAdded(ServletContextAttributeEvent scab)
HttpSessionAttributeListener.attributeAdded(HttpSessionBindingEvent se)
ServletRequestAttributeListener.attributeAdded(ServletRequestAttributeEvent srae)
attributeRemoved
从域对象中删除一个属性时调用。
ServletContextAttributeListener.attributeRemoved(ServletContextAttributeEvent scab)
HttpSessionAttributeListener.attributeRemoved(HttpSessionBindingEvent se)
ServletRequestAttributeListener.attributeRemoved(ServletRequestAttributeEvent srae)
attributeReplaced
从域对象中替换一个属性时调用。
ServletContextAttributeListener.attributeReplaced(ServletContextAttributeEvent scab)
HttpSessionAttributeListener.attributeReplaced(HttpSessionBindingEvent se)
ServletRequestAttributeListener.attributeReplaced(ServletRequestAttributeEvent srae)
public class MyAttributeListener implements ServletContextAttributeListener,
HttpSessionAttributeListener, ServletRequestAttributeListener {
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("servletContext对象中增加了一个名为" + scae.getName()
+ "的属性,该属性值为" + scae.getValue());
}
public void attributeRemoved(ServletContextAttributeEvent scae) {
System.out.println("servletContext对象中的" + scae.getName() + "属性被删除了");
}
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("servletContext对象中" + scae.getName() + "的属性值被替换成了"
+ scae.getValue());
}
public void attributeAdded(HttpSessionBindingEvent hbe) {
System.out.println("httpSession对象中增加了一个名为" + hbe.getName()
+ "的属性,该属性值为" + hbe.getValue());
}
public void attributeRemoved(HttpSessionBindingEvent hbe) {
System.out.println("httpSession对象中的" + hbe.getName() + "属性被删除了");
}
public void attributeReplaced(HttpSessionBindingEvent hbe) {
System.out.println("httpSession对象中" + hbe.getName() + "的属性值被替换成了"
+ hbe.getValue());
}
public void attributeAdded(ServletRequestAttributeEvent srae) {
System.out.println("servletRequest对象中增加了一个名为" + srae.getName()
+ "的属性,该属性值为" + srae.getValue());
}
public void attributeRemoved(ServletRequestAttributeEvent srae) {
System.out.println("servletRequest对象中的" + srae.getName() + "属性被删除了");
}
public void attributeReplaced(ServletRequestAttributeEvent srae) {
System.out.println("servletRequest对象中" + srae.getName() + "的属性值被替换成了"
+ srae.getValue());
}
感知Session绑定的事件监听器
保存到Session域中的对象可以有多种状态:绑定(保存)到Session域中、从Session域中解除绑定、随Session对象持久化(钝化)到一个存储设备中,随Session对象从一个存储设备中恢复(活化)。在Servlet规范中还定义了两个特殊的监听器接口来帮助JavaBean对象了解自己在Session域中的这些状态,这两个接口的名称分别为HttpSessionBindingListener和HttpSessionActivationListener,实现这两个接口的类不需要在web.xml文件中注册。只有要绑定到Session域中的Java对象才可能需要实现这两个接口,实现了HttpSessionBindingListener接口的JavaBean对象可以感知自己被绑定到Session中和从Session中删除的事件,实现了HttpSessionActivationListener接口的JavaBean对象可以感知自己被活化和钝化的事件。实现了这两个接口的JavaBean对象可以完成一些特殊功能,例如,可以在自己被钝化时释放与数据库的连接,在活化时重新与数据库建立连接。
HttpSessionBindingListener接口
当JavaBean对象被绑定到HttpSession对象中和从HttpSession对象中解除绑定时分别调用以下两个方法。
valueBound(HttpSessionBindingEvent event)
valueUnbound(HttpSessionBindingEvent event)
HttpSessionActivationListener接口
sessionWillPassivate(HttpSessionEvent se):HttpSession对象钝化之前被容器调用
sessionDidActivate(HttpSessionEvent se):HttpSession对象激活之后被容器调用
示例
public class MyBean implements HttpSessionBindingListener,
HttpSessionActivationListener, Serializable {
// 该方法被调用时,打印出对象将要被绑定的信息
public void valueBound(HttpSessionBindingEvent hbe) {
System.out.println("当前Session的ID标识为" + hbe.getSession().getId());
System.out.println("对象被绑定到这个Session对象中的" + hbe.getName() + "属性上");
}
// 该方法被调用时,打印出对象将要被解除绑定的信息
public void valueUnbound(HttpSessionBindingEvent hbe) {
System.out.println("当前Session的ID标识为" + hbe.getSession().getId());
System.out.println("对象将要从这个Session对象中的" + hbe.getName() + "属性上解除绑定");
}
public void sessionWillPassivate(HttpSessionEvent hse) {
System.out.println("对象将被持久化到文件系统中");
}
public void sessionDidActivate(HttpSessionEvent hse) {
System.out.println("对象从文件系统中恢复了");
}
}
国际化
与国际化相关的Java类
Locale类
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant)
语言参数(language)是一个有效的 ISO 语言代码。这些代码是由 ISO-639 定义的小写两字母代码。在许多网站上都可以找到这些代码的完整列表,如:http://www.loc.gov/standards/iso639-2/php/English_list.php
国家/地区参数(country)是一个有效的 ISO 国家/地区代码。这些代码是由 ISO-3166 定义的大写两字母代码。在许多网站上都可以找到这些代码的完整列表,如:http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
变量参数(variant)是特定于供应商或浏览器的代码。例如,用 WIN 代表 Windows、MAC 代表 Macintosh 和 POSIX 代表 POSIX。有两个变量时,把它们用下划线区分开来,把最重要的放在前面。例如,一个传统的西班牙排序规则可能用下列语言、国家/地区和变量参数来构造一个语言环境:"es"、"ES"、"Traditional_WIN"。
JDK中的一些本地敏感的类也提供了方法来返回它们所支持的所有本地信息对应时Locale实例对象的数组,例如,java.text.DateFormat类和java.text.NumberFormat类中都定义了一个getAvailableLocales静态方法,用于返回它们所支持的所有本地信息对应的Locale实例对象的数组。
Locale 类提供了一些方便的常量,可用这些常量为常用的语言环境创建 Locale 对象。例如,下面的内容为美国创建了一个 Locale 对象: Locale.US
String getDisplayName():返回适合向用户显示的语言环境名。这将把 getDisplayLanguage()、getDisplayCountry() 和 getDisplayVariant() 返回的值组合到单个字符串中。
创建完 Locale 后,就可以查询有关其自身的信息。使用 getCountry 可获取 ISO 国家/地区代码,使用 getLanguage 则获取 ISO 语言代码。可用使用 getDisplayCountry 来获取适合向用户显示的国家/地区名。同样,可用使用 getDisplayLanguage 来获取适合向用户显示的语言名。有趣的是,getDisplayXXX 方法本身是语言环境敏感的,它有两个版本:一个使用默认的语言环境作为参数,另一个则使用指定的语言环境作为参数。
Locale类提供了获得ISO语言代码和ISO国家代码的静态方法,例如,静态方法getISOLanguages用于返回包含ISO-639定义的所有的语言代码的数组,静态方法getISOCountries用于返回包含ISO-3166定义的所有的国家代码的数组。
Java 2 平台提供了多个可执行语言环境敏感操作的类:例如,NumberFormat 类以语言环境敏感的方式格式化数值、货币或百分比:
NumberFormat.getInstance(myLocale)
NumberFormat.getCurrencyInstance(myLocale)
NumberFormat.getPercentInstance(myLocale)
使用getDefault静态方法可以返回代表操作系统当前设置的本地信息的Locale实例对象。
示例:
public class LocaleExam {
public static void main(String[] args) {
Locale locale = new Locale("de", "CH");
System.out.println("德文地区的ISO语言代码:" + locale.getLanguage());
System.out.println("德文中的“德文”:" + locale.getDisplayLanguage(locale));
System.out.println("中文中的“德文”:" + locale.getDisplayLanguage(Locale.CHINA));
System.out.println("德文(瑞士)的Locale对象按操作系统的" + "默认本地方式显示的名称为:"
+ locale.getDisplayName());
System.out.println("德文(瑞士)的Locale对象按德文(瑞士)的" + "本地方式显示的信息为:"
+ locale.getDisplayName(locale));
}
}
德文地区的ISO语言代码:de
德文中的“德文”:Deutsch
中文中的“德文”:德文
德文(瑞士)的Locale对象按操作系统的默认本地方式显示的名称为:德文 (瑞士)
德文(瑞士)的Locale对象按德文(瑞士)的本地方式显示的信息为:Deutsch (Schweiz)
DateFormat类
DateFormat类定义了一些用于描述日期/时间的显示模式的int类型的常量,包括FULL、LONG、MEDIUM、DEFAULT和SHORT,这些常量用于描述显示日期/时间的字符串的长度,其中常量DEFAULT表示默认的显示模式,它的值为MEDIUM。这些常量所表示的日期/时间的确切格式取决于具体的国家地区,例如,对于日期/时间“2005年9月15日下午4时41分20秒”,它的各种模式的表现形式分别如下:
.SHORT模式完全是数字,在中文(中国)本地环境下,这个日期/时间显示为 “05-9-15 下午4:41”,(日期和时间之间有一个空格);在英文(美国)本地环境下,这个日期/时间显示为“9/15/05 4:41 PM”。
.MEDIUM模式比SHORT模式长些,在中文(中国)本地环境下,这个日期/时间显示为“2005-9-15 16:41:20”;在英文(美国)本地环境下,这个日期/时间显示为“Sep 15,2005 4:41:20 PM”。
.LONG模式比MEDIUM模式更长一些,在中文(中国)本地环境下,这个日期/时间显示为“2005年9月15日 下午04时41分20秒”;在英文(美国)本地下,这个日期/时间显示为“September 15,2005 4:41:20 PM CST”。
.FULL模式指定日期/时间的完整格式,在中文(中国)本地环境下,这个日期/时间显示为“2005年9月15日 星期四 下午04时41分20秒 CST”;在英文(美国)本地环境下,这个日期/时间显示为“Thursday,September 15,2005 4:41:20 PM CST”。
获得DateFormat类实例对象
DateFormat本身是一个抽象类,不能使用构造方法创建其实例对象,JDK提供了获得其实例对象(更确切地说,是它的子类的实例对象)的静态方法。获得DateFormat实例对象的静态方法可以分为三类。
1) 不接收任何参数,它们使用操作系统的本地信息和默认的日期/时间显示模式,主要包括下面几个方法。
l getInstance():获得日期和时间的显示模式都为SHORT的默认DateFormat实例对象。
l getDateInstance():以操作系统的本地信息和默认的日期显示摸式来获得DateFormat实例对象,该实例对象不处理时间值部分。
l getTimeInstance():以操作系统的本地信息和默认的时间显示模式来获得DateFornmt实例对象,该实例对象不处理日期值部分。
l getDateTimeInstance():以操作系统的本地信息和默认的日期/时间显示模式来获得DateFormat实例对象。
2) 接收用于指定日期/时间的显示模式的参数,它们使用操作系统默认的本地信息,主要包括下面几个方法。
l getDateInstance(int style):以操作系统的本地信息和指定的日期显示模式来获得DateFormate实例对象,该实例对象不处理时间值部分。
l getTimeInstance(int style):以操作系统的本地信息和指定的时间显示模式来获得DateFormat实例对象,该实例对象不处理日期值部分。
l getDateTimeInstance(int dateStyle,int timeStyle):以操作系统的本地信息和单独指定的日期与时间显示模式来获得DateFormat实例对象。
3) 接收用于指定日期/时间的显示模式和指定本地信息的参数,主要包括下面几个方法:
l getDateInstance(int style, Locale aLocale):指定的日期显示模式和本地信息来获得DateFormate实例对象,该实例对象不处理时间值部分。
l getTimeInstance(int style, Locale aLocale):指定的时间显示模式和本地信息来获得DateFormat实例对象,该实例对象不处理日期值部分。
l getDateTimeInstance(int dateStyle,int timeStyle, Locale aLocale):指定的日期与时间显示模式和本地信息来获得DateFormat实例对象。
日期/时间的格式化和解析
DateFormat类的format方法用于将日期/时间对象格式化符合某个本地环境习惯的字符串,DateFormat类的parse方法将符合某个本地环境习惯的日期/时间字符串解析为日期/时间对象:
1、 将当前时间格式化为符合操作系统的本地环境习惯的字符串:
String dateTimeString = DateFormat.getDateTimeInstance().format(new Date());
2、 将一个符合英语(美国)地区的日期字符串解析为Date对象:
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.US);
Date date=df.parse("September 15 , 2005");
DateFormat类解析日期字符串时,默认使用不严格的方式进行解析,它可将一个不合
法的日期转换为合法的日期(但日期时间字符串格式要合法,否则解析抛异常),例如,日期字符串“2007-2-30”将被自动转换为“2007-3-2”。
DateFormat类的setLenient(boolean lenient)方法可以设置是否以严格的方式解析日期字符串,如果设置该方法的参数值为true,就以不严格的方式解析;如果设置该方法的参数值为false,就以严格的方式解析,对类似“2007-2-30”这样的不合法的日期字符串抛出ParseException异常。
示例
public class DateFormatExam {
public static void main(String[] args) {
System.out.println("使用默认的本地信息、默认时区和" + "默认的时间/日期模式格式化当前日期:");
DateFormat df = DateFormat.getDateTimeInstance();
Date date = new Date();
System.out.println(df.format(date));
System.out.println("===========================================");
System.out.println("将格式化的日期结果追加到指定字符串的后面" + "并跟踪月份在结果字符串中的索引位置:");
StringBuffer sb = new StringBuffer("xxx:");
FieldPosition fp = new FieldPosition(DateFormat.MONTH_FIELD);
String dateString = df.format(date, sb, fp).toString();
System.out.println(dateString);
System.out.println("月份部分的第一个字符在整个结果字符串中" + "的索引位置为:"
+ fp.getBeginIndex() + ";月份部分的最后一个字符后面的字符在整个字符串中的索引为:"
+ fp.getEndIndex());
System.out.println("===========================================");
df.setTimeZone(TimeZone.getTimeZone("Pacific/Noumea"));//设置时区
System.out.println("使用Pacific/Noumea时区格式化当前日期:" + df.format(date));
}
}
使用默认的本地信息、默认时区和默认的时间/日期模式格式化当前日期:
2010-8-2 10:20:36
===========================================
将格式化的日期结果追加到指定字符串的后面并跟踪月份在结果字符串中的索引位置:
xxx:2010-8-2 10:20:36
月份部分的第一个字符在整个结果字符串中的索引位置为:9;月份部分的最后一个字符后面的字符在整个字符串中的索引为:10
===========================================
使用Pacific/Noumea时区格式化当前日期:2010-8-2 13:20:36
public class DateParseExam {
public static void main(String[] args) throws ParseException {
System.out.println("使用默认本地信息解析日期/时间字符串" + "2005-9-15 16:41:20:");
DateFormat df1 = DateFormat.getDateTimeInstance();
df1.setTimeZone(TimeZone.getTimeZone("Pacific/Noumea"));
System.out.println(df1.parse("2005-9-15 16:41:20"));
System.out.println("===========================================");
System.out.println("使用默认本地信息、不严格的方式解析日期" + "字符串2005-2-30:");
DateFormat df2 = DateFormat.getDateInstance();
System.out.println(df2.parse("2005-2-30"));
System.out.println("===========================================");
System.out.println("使用严格的方式解析日期字符串2005-2-30:");
df2.setLenient(false);
System.out.println(df2.parse("2005-2-30"));
}
}
使用默认本地信息解析日期/时间字符串2005-9-15 16:41:20:
Thu Sep 15 13:41:20 CST 2005
===========================================
使用默认本地信息、不严格的方式解析日期字符串2005-2-30:
Wed Mar 02 00:00:00 CST 2005
===========================================
使用严格的方式解析日期字符串2005-2-30:
Exception in thread "main" java.text.ParseException: Unparseable date: "2005-2-30"
at java.text.DateFormat.parse(DateFormat.java:335)
at a.DateParseExam.main(DateParseExam.java:22)
NumberFormat类
获得NumberFormat类实例对象
NumberFormat是一个抽象类,不能使用构造方法创建其实例对像,JDK提供了获得NumberFormat实例对象(更确切地说,是它的子类的实例对象)的静态方法,这与DateFormat类很类似。获得NumberFormat实例对象的静态方法可以分为两类:
1) 不接收任何参数,它们使用操作系统的本地信息,主要包括下面几个方法:
l getInstance()和getNumberInstance():以操作系统的本地信息来获得其有多种用途的NumberFomrat实例对象。
l getIntegerInstance():以操作系统的本地信息来获得处理整数的NumberFormat实例对象。
l getCurrencyInstance():以操作系统的本地信息来获得处理货币的NumberFormat实例对象。
l getPercentInstance():以操作系统的本地信息来获得处理百分比数值的NumberFormat实例对象。
2) 在上面的方法上分别加上一个Locale参数,这里就不写了。
数值的格式化和解析
1、 将一个数值格式化为符合操作系统本地一半习惯的字符串:
String numberString = NumberFormat.getInstance().format(12345);
2、 将一个符合德语(德国)地区习惯的数值字符串解析为Number对象:
NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
Number number = nf.parse("12.345");
示例
public class NumberFormatExam {
public static void main(String[] args) {
NumberFormat nf1 = NumberFormat.getNumberInstance();
System.out.println("使用默认的本地信息格式化数值,将格式化的结果"
+ "追加到指定字符串的后面并跟踪整数部分在结果字符串中的索引位置:");
StringBuffer sb = new StringBuffer("xxx:");
FieldPosition fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
String numberString = nf1.format(12345.6, sb, fp).toString();
System.out.println(numberString);
System.out.println("整数部分的第一个字符在整个结果字符串中的索引" + "位置为:"
+ fp.getBeginIndex());
System.out.println("整数部分的最后一个字符后面的字符在整个结果字" + "符串中的索引位置为:"
+ fp.getEndIndex());
System.out.println("============================================");
System.out.print("格式化后的结果总是显示小数点:");
((DecimalFormat) nf1).setDecimalSeparatorAlwaysShown(true);
System.out.println(nf1.format(12345));
System.out.print("格式化后的数值结果不使用组分隔符且小数部分最少包含2位数字:");
nf1.setMinimumFractionDigits(2);
nf1.setGroupingUsed(false);
System.out.println(nf1.format(12345));
System.out.println("============================================");
System.out.print("使用默认的本地信息将数值格式化为货币格式:");
NumberFormat nf2 = NumberFormat.getCurrencyInstance();
System.out.println(nf2.format(12345));
System.out.print("使用德文本地信息将数值格式化为货币格式:");
NumberFormat nf3 = NumberFormat.getCurrencyInstance(Locale.GERMAN);
System.out.println(nf3.format(12345));
}
}
使用默认的本地信息格式化数值,将格式化的结果追加到指定字符串的后面并跟踪整数部分在结果字符串中的索引位置:
xxx:12,345.6
整数部分的第一个字符在整个结果字符串中的索引位置为:4
整数部分的最后一个字符后面的字符在整个结果字符串中的索引位置为:10
============================================
格式化后的结果总是显示小数点:12,345.
格式化后的数值结果不使用组分隔符且小数部分最少包含2位数字:12345.00
============================================
使用默认的本地信息将数值格式化为货币格式:¥12,345.00
使用德文本地信息将数值格式化为货币格式:12.345,00 €
public class NumberParseExam {
public static void main(String[] args) throws Exception {
NumberFormat nf1 = NumberFormat.getInstance();
System.out.println("使用默认的本地信息,只解析数值字符串12345.67的整数部分:");
nf1.setParseIntegerOnly(true);
System.out.println(nf1.parse("12345.67"));
System.out.println("===========================================");
System.out.println("解析包含非数字字符的数值字符串1234A56.7a:"
+ nf1.parse("1234A56.7a"));
System.out.print("从字符串中的第三个字符开始解析数值字符串12345:");
ParsePosition pp = new ParsePosition(2);
System.out.println(nf1.parse("12345", pp));
System.out.print("使用德文本地信息解析百分数字符串125,3%:");
NumberFormat nf2 = NumberFormat.getPercentInstance(Locale.GERMAN);
System.out.println(nf2.parse("125,3%"));
}
}
使用默认的本地信息,只解析数值字符串12345.67的整数部分:
12345
===========================================
解析包含非数字字符的数值字符串1234A56.7a:1234
从字符串中的第三个字符开始解析数值字符串12345:345
使用德文本地信息解析百分数字符串125,3%:1.253
MessageFormat类
模式字符串可以直接在Java源文件中编写,但通常是写在.properties属性资源文件中由应用程序进行读取。模式字符串中的非占位符部分的字符文本可以用一对单引号引起来,也可以不用单引号引起来,细节如下:
l 如果将模式字符串中的非占位符部分的字符文本用一对单引号引起来,则非占位符部分的字符文本中可以包含除单引号之外的任何字符,格式化后的结果中将不包含用于将字符文本引起来的单引号。
l 如果没有将模式字符串中的非占位符部分用单引号引起来,则非占位符字符串可以包含除单引号和左花括号之外的任何字符。如果要在格式化后的非占位符字符串中包含一个字面意义的单引号,必须使用两个连续的单引号“‘‘”来表示,如果要在格式后的非占位符字符串中包含字面意义的左花括号,必须将其放在一对单引号内。如果占位符嵌套在一对单引号内,它就不再表示占位符,只是普通的字符,例如,如果要在格式化后的字符串中出现文本字符串“‘{0}‘”模式字符串中应该写成“‘‘‘{‘0}‘‘”或“‘‘‘{0}‘‘‘”
简单示例
public class MessageFormatExam1 {
public static void main(String args[]) throws Exception {
String pattern = "On {0}, {1} destroyed {2} houses and caused "
+ "{3} of damage.";
MessageFormat msgFmt = new MessageFormat(pattern);
// 准备参数数组
String datetimeString = "Jul 3, 1998 12:30 PM";
Date date = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.SHORT, Locale.US).parse(datetimeString);
String event = "a hurricance";
Object[] msgArgs = { date, event, new Integer(99), new Double(1E7) };
// 执行格式化操作
String result = msgFmt.format(msgArgs);
System.out.println(result);
}
}
On 98-7-3 下午12:30, a hurricance destroyed 99 houses and caused 10,000,000 of damage.
下面与上面程序作用一样,结果也一样:
public class MessageFormatExam1 {
public static void main(String args[]) throws Exception {
String datetimeString = "Jul 3, 1998 12:30 PM";
Date date = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.SHORT, Locale.US).parse(datetimeString);
String event = "a hurricance";
String pattern = "On {0}, {1} destroyed {2} houses and caused {3} of damage.";
String result = MessageFormat.format(pattern, date, event, new Integer(
99), new Double(1E7));
System.out.println(result);
}
}
在应用程序中动态设置占位符的格式类型和模式
在应用程序中可以调用MessageFormat类的setFormat、setFormatByArgumentIndex、setFormats或setFormatsByArgumentIndex等方法来设里占位符的格式类型和模式,而不是使用相应数据的默认格式类型和默认模式,例如,如果希望将上面程序中的日期/时间格式化为非默认的模式,将第4个占位符格式化为货币格式,这时就应该在调用format方法之调用setFormat、setFormatByArgumentIndex、setFormats或setFormatsByArgumentIndex等方法。
public class MessageFormatExam3 {
public static void main(String args[]) throws Exception {
String pattern = "At {0} on {1}, {2} destroyed {3} houses and "
+ "caused {4} of damage.";
MessageFormat msgFmt = new MessageFormat(pattern);
String datetimeString = "Jul 3, 1998 12:30 PM";
Date date = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.SHORT, Locale.US).parse(datetimeString);
String event = "a hurricance";
Object[] msgArgs = { date, date, event, new Integer(99),
new Double(1E7) };
msgFmt.setFormatByArgumentIndex(0, DateFormat
.getTimeInstance(DateFormat.SHORT));
msgFmt.setFormatByArgumentIndex(1, DateFormat.getDateInstance());
msgFmt.setFormatByArgumentIndex(4, NumberFormat.getCurrencyInstance());
/*
* 或者是这样: Format[] argsFmt = {
* DateFormat.getTimeInstance(DateFormat.SHORT),
* DateFormat.getDateInstance(), null, null,
* NumberFormat.getCurrencyInstance() };
* msgFmt.setFormatsByArgumentIndex(argsFmt);
*/
String result = msgFmt.format(msgArgs);
System.out.println(result);
}
}
At 下午12:30 on 1998-7-3, a hurricance destroyed 99 houses and caused ¥10,000,000.00 of damage.
直接在占位符中设置占位符的格式类型和模式
在MessageFormat类格式化的模式字符串中也可以采用如下两种格式的占位符:
l {ArgumentIndex , FormatType}
l {ArgumentIndex , FormatType ,FormatStyle}
这两种占位符直接指定了占位符的格式类型和模式,MessegeFormat类将根据占位符中的FormatType和FormatStyle来创建执行具体的格式化操作的格式化实例对象。其中FormatType表示格式化类型,它的值可以是number,date,time,choice 4种格式,FormatStyle表示FormatType指定的格式类型下的某种模式,它的设置值如下:
l 如果FormatType的值为number,则FormatStyle的值可以是integer、currency、percent 3种标准模式,除此之外,FormatStyle的值也可以是用户自定义的表示数值格式的模式文本,例如,“#,##0.0#”表示数值的整数部分最多显示4位,并按3位进行分组,小数部分最多显示两位,如果整数部分为0,则显示一个0,如果小数据部分为0,则也显示一个0;“#”和“0”都表示显示一个数值位,如果数值中不存在“#”对应的位时,则什么也不显示,如果数值中不存在“0”对应的位时,则显示字符0。关于数值格式的自定的模式更多信息,请参数DecimalFormat类的帮助文档。
l 如果FormatType的值为date或time,则FormatStyle值可以是short、medium、long、full 4种标准模式,除此之外,FormatStyle的值也可以是用户自定表示日期/时间格式的模式文本,如果“yyyy-MM-dd HH:mm:ss”。关于日期时间格式的自定的模式具体信息,请参数SimpleDateFormat类的帮助文档。
public class MessageFormatExam4 {
public static void main(String args[]) throws Exception {
String pattern = "At {0, time, short} on {0, date}, {1} destroyed "
+ "{2,number,#,##0.00#} houses and caused {3, number, currency} of damage.";
MessageFormat msgFmt = new MessageFormat(pattern);
String datetimeString = "Jul 3, 1998 12:30 PM";
Date date = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.SHORT, Locale.US).parse(datetimeString);
String event = "a hurricance";
Object[] msgArgs = { date, event, new Integer(9999999), new Double(1E7) };
String result = msgFmt.format(msgArgs);
System.out.println(result);
}
}
At 下午12:30 on 1998-7-3, a hurricance destroyed 9,999,999.00 houses and caused ¥10,000,000.00 of damage.
l 对于更复杂的模式,可以使用 ChoiceFormat
来生成正确的单数和复数形式:
public static void main(String args[]) throws Exception {
MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
double[] filelimits = { 0, 1, 2 };
//在ChoiceFormat模式占位符中指定模式
String[] filepart = { "no files", "one file", "{0,number} files" };
ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
form.setFormatByArgumentIndex(0, fileform);
int fileCount = 1273;
String diskName = "MyDisk";
Object[] testArgs = { new Long(fileCount), diskName };
System.out.println(form.format(testArgs));
}
The disk "MyDisk" contains 1,273 files.
如上例所示,可以以编程方式来创建 ChoiceFormat,或使用模式创建。有关更多信息,请参阅 ChoiceFormat:
public static void main(String args[]) throws Exception {
double[] filelimits = { 0, 1, 2 };
//ChoiceFormat中占位符中未指定格式
String[] filepart = { "are no files", "is one file", "are {2} files" };
ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
//通过编程方式来指定
Format[] testFormats = { fileform, null, NumberFormat.getInstance() };
MessageFormat pattform = new MessageFormat("There {0} on {1}");
pattform.setFormats(testFormats);
Object[] testArgs = { null, "ADisk", null };
for (int i = 0; i < 4; ++i) {
testArgs[0] = new Integer(i);
testArgs[2] = testArgs[0];
System.out.println(pattform.format(testArgs));
}
}
There are no files on ADisk
There is one file on ADisk
There are 2 files on ADisk
There are 3 files on Adisk
或者是这样也可以:
public class MessageFormatExam4 {
public static void main(String args[]) throws Exception {
MessageFormat form = new MessageFormat(
"There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files} on {1}.");
String diskName = "ADisk";
Object[] testArgs = { null, diskName };
for (int i = 0; i < 4; ++i) {
testArgs[0] = new Integer(i);
System.out.println(form.format(testArgs));
}
}
}
结果与前面程序输出一样。
ResourceBundle类
资源文件完全遵循java.util.Properties类要求的文件格式,它要求资源文件中的字符必须全部为有效的ASCII字符,如果资源文件中要包含非ASCII的字符,必须将它们转换成“\uXXXX”形式的转义序列,其中,XXXX是该字符的Unicode编码的十六进制数值。
native2ascii -encoding gb2312 temp.properties MyResources_zh_CN.properties
装载资源包与读取资源信息
ResourceBundle类提供了存放和管理资源包的功能,调用ResourceBundle类的静态方法getBundle可以获得ResourceBundle类的实例对象,getBundle方法有如下三种重载的形式:
public static final ResourceBundle getBundle(String baseName)
public static final ResourceBundle getBundle(String baseName, Locale locale)
public static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader)
getBundle 使用基本名称、指定的语言环境和默认语言环境(从 Locale.getDefault 获得)来生一系列要查找的资源文件的名称,然后依次查找这些资源文件,直到找到第一个最匹配的资源文件为止。如果指定语言环境的语言、国家和变量都是空字符串,则基本名称就是惟一的候选包名称。否则,从指定语言环境 (language1, country1, and variant1) 和默认语言环境 (language2, country2, and variant2) 的属性值生成下列查找序列:
baseName + "_" + language1 + "_" + country1 + "_" + variant1
baseName + "_" + language1 + "_" + country1
baseName + "_" + language1
baseName + "_" + language2 + "_" + country2 + "_" + variant2
baseName + "_" + language2 + "_" + country2
baseName + "_" + language2
baseName
假设pak1.pak2包下有 a.properties 资源文件,则可以使用以下几种方式来加载:
ResourceBundle rb =ResourceBundle.getBundle("pak1.pak2.a");
ResourceBundle rb =ResourceBundle.getBundle("pak1/pak2/a");
ResourceBundle rb =ResourceBundle.getBundle("pak1/pak2.a");
如果传递给getBundle方法的baseName参数中包含了“.”字符,则将被全部替换成“/”字符,用作目录路径分隔符。注,路径不能以“/”开头。
ResourceBundle是一个抽象类,ResourceBundle.getBundle方法返回的实际上是ResourceBundle的一个具体子类的实例对象。JDK中提供的PropertyResourceBundle类是ResourceBundle类的一个具体子类,它可以从Properties格式的文件中加载每项资源信息。如果getBundle方法找到了合适的资源文件,将以该资源文件中的内容创建一个PropertyResourceBundle实例对象并返回这个实例对象。如果在整个资源包中没有找到任何一个合适的资源文件,ResourceBundle.getBundle方法将抛出异常。
ResourceBundle类的扩展应用
ResourceBundle类提供了存放和管理一个资源包中的资源信息的功能。ResourceBundle是一个抽象类,ResourceBundle.getBundle方法返回的是ResourceBundle的一个具体子类的实例对象。JDK中的PropertyResourceBundle类提供了管理资源信息的简单方式,如果这个类不能满足应用程序的需求,用户还可以自己定义ResourceBundle的具体子类来管理资源信息,并让ResourceBundle.getBundle返回用户自己定义的ResourceBundle具体子类的实例对象。
其实,ResourceBundle.getBundle方法在查找和加载某个资源包中的某个本地环境对应的资源文件时,它并不是只加载与该名称对应的Properties文件,而是首先加载与资源名对应的ResourceBundle的具体子类,当该名称的ResourceBundle的具体子类不存在时,getBundle方法才去加载与该资源名对应的Properties文件。如果getBundle方法找到与资源名对应的ResourceBundle的具体子类,则创建该类的一个实例对象并返回这个实例对象;否则,getBundle方法接着查找与这个资源名对应的Properties文件,以该Properties文件中的内容创建一个PropertyResourceBundle实例对象并返回这个实例对象。如果在整个资源包中没有找到任何一个合适的资源文件,ResourceBundle.getBundle方法将抛出异常。
自定义ResourceBundle的子类
ResourceBuudle类中定义了getKeys和handleGetObject两个抽象方法,getKeys用于返回包含该资源包中的所有资源项名称的集合,handleGetObject用于返回某个资源项名称所对应的值内容。自定义的ResourceBundle的具体子类必须覆盖handleGetObject和getKeys
这两个抽象方法,handleGetObject方法返回的类型为Object,也就是说,资源包中的资源项不仅限于字符串,而可以是任意的对象。所以,ResourceBundle类中除了定义getString方法来获得字符串类型的资源项外,还定义了获取其他类型的资源项的方法,包括:
.getStringArray:获取字符串数组类型的资源项的值
.getObject:获取任意对象类型的资源项的值
由于getObject方法的返回值为Object类型,所以,使用getObject方法获取资源项的值后,必须将得到的结果转换成资源项的实际类型。
===================MyResources.java===================
public class MyResources extends ResourceBundle {
private String[] keys = { "OkKey", "CancelKey" };
static {
System.out.println("缺省资源正在被装载!");
}
public Object handleGetObject(String key) {
if (key.equals("OkKey")) {
return "Ok";
} else if (key.equals("CancelKey")) {
return "Cancel";
}
return null;
}
public Enumeration getKeys() {
return Collections.enumeration(Arrays.asList(keys));
}
}
ResourceBundle.getBundle方法装载一个资源包时,一定会装载其中的默认资源,而不管是否使用。
===================MyResources_de.java===================
public class MyResources_de extends ResourceBundle {
private String[] keys = { "OkKey", "CancelKey" };
static {
System.out.println("德文资源正在被装载!");
}
public Object handleGetObject(String key) {
if (key.equals("OkKey")) {
return "Ok";
} else if (key.equals("CancelKey")) {
return "Abschafung";
}
return null;
}
public Enumeration getKeys() {
return Collections.enumeration(Arrays.asList(keys));
}
}
===================测试===================
public static void main(String args[]) {
Locale locale = Locale.GERMANY;// 德国人
ResourceBundle myResources = ResourceBundle.getBundle("MyResources",
locale);
System.out.println("OkKey:" + myResources.getString("OkKey"));
System.out.println("CancelKey:" + myResources.getString("CancelKey"));
}
ResourceBundle.getString方法读取某个资源项的值时,如果当前的ResourceBundle对象没有该资源,getString方法将从默认资源中读取该资源项。由于上面的MyResources_de类与MyResources类中的资源项“OkKey”的值完全相同,所以,即使MyResources_de类中的handleGetObject方法不返回资源项“OkKey”的值,程序的运行结果也完全一样。
由于MyResources_de类与MyResources类的getKeys方法的代码完全相同,所以,如果让MyResources_de类继承MyResources,而不是直接继承ResourceBundle类,这样,MyResources_de类只需要覆盖handleGetObject方法即可,将共用的资源或默认的资源写在MyResources类中,而涉及到具体某国资源时,写在其子类中:
public class MyResources_de extends MyResources {
public Object handleGetObject(String key) {
if (key.equals("CancelKey")) {
return "Abschafung";
}
return null;
}
}
如果此时编写一个MyResources_de.properties 属性文件,并将上面的MyResources_de.class文件删除掉,则上面测试程序会从该属性文件中读取,否则还是从相应的.class读取:
=================== MyResources_de.properties==================
OkKey=Ok
CancelKey=Abschafung
总结:
l ResourceBundle.getBundle方法加载资源文件时,某个特定本地环境所对应的 .class资源文件的优先级高于该本地环境所对应的 .properties 资源文件,如果两者同时存在,则getBundle方法加载.class文件中的内容。
l ResourceBundle.getBundle方法在装载某个资源包时,总是装载资源包中的默认资源,而不是在特定本地环境所对应的资源文件中找不到某个资源项时才去装载默认资源;如果在特定本地环境所对应的资源文件中找不到指定的资源项,将在默认资源中查找该资源项的值。
l 如果资源包中所有资源信息的值都是字符串,则应用程序中可以使用properties文件管理资源信息,也可以使用自定义的 ResourceBundle 的具体子类来管理资源信息,前者更灵活方便。
ListResourceBundle类
为了简化自定义的ResourceBundle子类的编写,JDK中定义了一个ListResouseBundle类,ListResourceBundle类是ResourceBundle类的一个抽象子类,它实现了ResourceBundle类的 getKeys 和 handleGetObject 这两个方法。ListResourceBundle 类实现的这两个方法内部调用了ListResourceBundle类中定义的一个getContents抽象方法,getContents方法返回一个包含所有资源项的名称和值的二维数组,ListResourceBundle类的子类只需要实现getContents方法,就自然实现了getKeys和handleGetObject这两个方法,这使得ListResourcBundle类的子类可以使用一个简单的包含所有资源项的名称和值的二维数组来管理资源信息:
public class fdas extends ListResourceBundle {
static final Object[][] contents = { { "OkKey", "Ok" },
{ "CancelKey", "Cancel" } };
public Object[][] getContents() {
return contents;
}
}
Web应用的国际化
实现Web应用的国际化有两种方式:
l 针对不同语言和地区的用户开发出不同的JSP网页版本,当用户请求资源时,根据请求消息中携带的本地信息为用户提供合适的版本。
l 将对本地环境敏感的资源数据(例如,错误提示信息、菜单或按妞上的标题文字)从网页中分离出来,放在.properties属性资源文件中。对于应用中的数值、货币和日期/时间等本地敏感的数据,可以通过占位符的方式设置它们的格式类型和格式模式。
如果整个Web应用或Web应用中有大量数据需要根据用户的本地信息进行显示,则可以采用第一种方式;如果Web应用中只有少量数据需要根据用户的本地信息进行显示,则采用第二种方式可以大大提高开发效率。事实上,这两种方式往往结合使用。
获取Web应用中的本地信息
要实现Web应用的国际化,首先要获得客户端浏览器的本地信息。大多数Web浏览器通常会在HTTP请求消息中通过Accept-Language消息头附带自己的本地信息,Web容器则可以根据HTTP请求消息中附带的Accept-Language头信息自动创建出标识客户端本地信息的Locale对象。在Servlet程序中,调用HttpServletRequest对象的getLocale方法和getLocales方法,就可以获得代表客户端本地信息的Locale对象。getLocale方法和getLocales方法都可以获得HTTP请求消息中的Accept-Language头字段中设置的本地信息,其中getLocale方法返回代表客户端的首选本地信息的Locale对象,getLocales方法返回一个包含客户端支持的所有本地信息的Locale对象的Enumeration集合对象,这些Locale对象按照客户端支持的所有本地信息的优先级在集合中依次排列。如果客户端的HTTP请求消息中没有提供Accept-Language头字段,则getLocale方法返回代表服务器端的默认本地信息的Locale对象,getLocales方法返回的Enumeration对象中也只包含这个代表服务器端的默认本地信息的Locale对象。
Web应用的国际化示例
=================== mypakg.applicationRes_en_US.properties ===================
i18n.title=The i18n of the Web
i18n.heading=Hello World! A i18n program
i18n.message=Today is {0, date, long}, time is {0, time, medium}. I bought {1} books, and spent {2, number, currency}.
=================== application_temp.properties ===================
i18n.title=Web应用的国际化
i18n.heading=你好!一个国际化应用程序
i18n.message=今天是{0, date, long},现在时间是{0, time, medium}。我今天买了{1}本书,共花了{2, number, currency}.
D:\eclipse_workspace\servlet_jsp\src>native2ascii -encoding gb2312 application_t emp.properties application_zh.properties
=================== mypakg.application_zh_CN.properties ===================
i18n.title=Web\u5e94\u7528\u7684\u56fd\u9645\u5316
i18n.heading=\u4f60\u597d\uff01\u4e00\u4e2a\u56fd\u9645\u5316\u5e94\u7528\u7a0b\u5e8f
i18n.message=\u4eca\u5929\u662f{0, date, long}\uff0c\u73b0\u5728\u65f6\u95f4\u662f{0, time, medium}\u3002\u6211\u4eca\u5929\u4e70\u4e86{1}\u672c\u4e66\uff0c\u5171\u82b1\u4e86{2, number, currency}.
=================== webi18n.jsp ===================
<%@ page contentType="text/html;charset=gb2312" %>
<%@ page import="java.util.ResourceBundle, java.text.MessageFormat" %>
<%@ page import="java.util.Date" %>
<%
ResourceBundle bundle = ResourceBundle.
getBundle("mypakg.applicationRes", request.getLocale());
%>
<html>
<head>
<title><%=bundle.getString("i18n.title")%></title>
</head>
<body>
<%=bundle.getString("i18n.heading")%><br /><br />
<%
String message = bundle.getString("i18n.message");
MessageFormat msgFmt = new MessageFormat(message, request.getLocale());
Date dateTime = new Date();
Object[] msgArgs = {dateTime, new Integer(10), new Double(550.8)};
%>
<%=msgFmt.format(msgArgs) %>
</body>
</html>