ApplicationContext
1、ApplicationContext的继承关系
从上图中可以发现,ApplicationContext接口继承了很多接口,这些接口我们可以将其分为五类:
- MessageSource,主要用于国际化
- ApplicationEventPublisher,提供了事件发布功能
- EnvironmentCapable,可以获取容器当前运行的环境
- ResourceLoader,主要用于加载资源文件
- BeanFactory,负责配置、创建、管理Bean,IOC功能的实现主要就依赖于该接口子类实现。
关于这些的接口的具体功能的介绍在后文会介绍,当前我们需要知道最重要的一点就是ApplicationContext继承了BeanFactory接口,也就是说它具有BeanFactory所有的功能。
2、ApplicationContext的功能
Spring中的国际化(MessageSource)
国际化是什么?
应用程序运行时,可根据客户端操作系統的国家/地区、语言的不同而显示不同的界面,比如客户端OS的语言环境为大陆的简体中文,程序就显示为简体中文,客户端OS的语言环境为美国——英语,程序就显示美式英语。OS的语言环境可在控制面板中手动设置。国际化的英文单词是Internationalization,单词较长,通常简称i18n,I是第一个字母,18表示中间省略了18个字母,N是最后一个字母。
假设我们正在开发一个支持多国语言的Web应用程序,要求系统能够根据客户端的系统的语言类型返回对应的界面:英文的操作系统返回英文界面,而中文的操作系统则返回中文界面——这便是典型的i18n国际化问题。对于有国际化要求的应用系统,我们不能简单地采用硬编码的方式编写用户界面信息、报错信息等内容,而必须为这些需要国际化的信息进行特殊处理。简单来说,就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。
JAVA中的国际化
国际化信息也称为本地化信息,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。
【部分国际化代码】:
ar_sa 阿拉伯语(沙特阿拉伯) ar_iq 阿拉伯语(伊拉克) eu 巴斯克语 bg 保加利亚语 zh_tw 中文(中国台湾) zh_cn 中文(中华人民共和国) zh_hk 中文(中国香港特别行政区) zh_sg 中文(新加坡) hr 克罗地亚语 en 英语 en_us 英语(美国) en_gb 英语(英国) en_au 英语(澳大利亚) en_ca 英语(加拿大)
本地化对象(Locale)
Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
Locale locale=new Locale("zh","cn");//中文,中国 Locale locale2=new Locale("en","us");//英文,美国 Locale locale3=new Locale("zh");//中文--不指定国家 Locale locale4=Locale.CHINA;//中文,中国 Locale locale5=Locale.CHINESE;//中文
在持有一个Locale对象后,我们需要将同一个文字或者数字根据不同的地区/语言格式化成不同的表现形式,所以这里我们还需要一个格式化的操作,JDK给我们提供以下几个常见的类用于国际化格式化
NumberFormat:可以处理数字,百分数,货币等。下面以货币为例:
public static void main(String[] args) { // 1.通过语言跟地区确定一个Locale对象 // 中国,中文 Locale chinaLocale = new Locale("zh", "cn"); // 美国,英文 Locale usLocale = new Locale("en", "us"); // 获取货币格式化对象 NumberFormat chinaCurrencyFormat = NumberFormat.getCurrencyInstance(chinaLocale); NumberFormat usLocaleCurrencyFormat = NumberFormat.getCurrencyInstance(usLocale); // 中文,中国下的货币表现形式 String chinaCurrency = chinaCurrencyFormat.format(99.9); // 输出 ¥99.90 // 美国,英文下的货币表现形式 String usCurrency = usLocaleCurrencyFormat.format(99.9); // 输出 $99.90 System.out.println(chinaCurrency); System.out.println(usCurrency); }
格式化对象
DateFormat:通过DateFormat#getDateInstance(int style,Locale locale)方法按本地化的方式对日期进行格式化操作。该方法第一个入参为时间样式,第二个入参为本地化对象
public static void main(String[] args) { // 1.通过语言跟地区确定一个Locale对象 // 中国,中文 Locale chinaLocale = new Locale("zh", "cn"); // 美国,英文 Locale usLocale = new Locale("en", "us"); DateFormat chinaDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,chinaLocale); DateFormat usDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,usLocale); System.out.println(chinaDateFormat.format(new Date())); // 输出 2020年1月15日 System.out.println(usDateFormat.format(new Date())); // 输出 January 15, 2020 }
MessageFormat:在NumberFormat和DateFormat的基础上提供了强大的占位符字符串的格式化功能,它支持时间、货币、数字以及对象属性的格式化操作
1.简单的占位符替换
public static void main(String[] args) { // 1.通过语言跟地区确定一个Locale对象 // 中国,中文 Locale chinaLocale = new Locale("zh", "cn"); String str1 = "{0},你好!你于{1}在农业银行存入{2}。"; MessageFormat messageFormat = new MessageFormat(str1,chinaLocale); Object[] o = {"小红", new Date(), 99.99}; System.out.println(messageFormat.format(o)); // 输出:小红,你好!你于20-1-15 下午4:05在农业银行存入99.99。 }
1.指定格式化类型跟格式化样式的占位符替换
public static void main(String[] args) { String str1 = "{0},你好!你于{1,date,long}在农业银行存入{2,number, currency}。"; MessageFormat messageFormat = new MessageFormat(str1,Locale.CHINA); Object[] o = {"小红", new Date(), 1313}; System.out.println(messageFormat.format(o)); // 输出:小红,你好!你于2020年1月15日在农业银行存入¥1,313.00。 }
在上面的例子中,0,1,2代表的是占位符的索引,从0开始计数。date,number为格式化的类型。long,currency为格式化样式。
- FormatType:格式化类型,取值范围如下:
number:调用NumberFormat进行格式化
date:调用DateFormat进行格式化
time:调用DateFormat进行格式化
choice:调用ChoiceFormat进行格式化
- FormatStyle::设置使用的格式化样式
short
medium
long
full
integer
currency
percent
SubformatPattern (子格式模式,形如#.##)
对于具体的使用方法就不多赘述了,大家可以自行百度。
资源文件的加载
在实现国际化的过程中,由于我们的用户界面信息、报错信息等内容都不能采用硬编码的方式,所以为了在不同的区域/语言环境下能进行不同的显示,我们需要为不同的环境提供不同的资源文件,同时需要遵循一定的规范。
- 命名规范:资源名_语言代码_国/地区代码.properties
举一个例子:假设资源名为content,语言为英文,国家为美国,则与其对应的本地化资源文件命名为content_en_US.properties。
下面以IDEA为例,创建资源文件并加载读取
1.创建资源文件,在Resource目录下,创建一个Bundle
2.添加需要兼容的区域/语言,我这里就添加一个英语/美国,给这个Bundle命名为i18n,名字随意
3.此时会在Resource目录下生成如下的目录结构
在两个配置文件中,我分别添加了两段配置:
【i18n.properties】:
#小明(资源文件对文件内容有严格的要求:只能包含ASCII字符,所以必须将非ASCII字符的内容转换为Unicode代码的表示方式) name=\u5c0f\u660e #他十九岁了 age=19
【i18n_en_US.properties】:
name=Xiaoming age=19
示例代码:
public static void main(String[] args) { // i18n要跟我们之前创建的Bundle的名称一致 // Locale.US指定了我们要拿这个Bundle下的哪个区域/语言对于的资源文件,这里获取到的是i18n_en_US.properties这个配置文件 ResourceBundle usResourceBundle = ResourceBundle.getBundle("i18n", Locale.US); System.out.println(usResourceBundle.getString("name")); // 输出Xiaoming System.out.println(usResourceBundle.getString("age")); ResourceBundle chinaResourceBundle = ResourceBundle.getBundle("i18n"); System.out.println(chinaResourceBundle.getString("name")); // 输出小明 System.out.println(chinaResourceBundle.getString("age")); }
Spring中的MessageSource
在聊完了JAVA中的国际化后,我们回归主题,ApplicationContext接口继承了MessageSource接口,MessageSource接口又提供了国际化的功能,所以ApplicationContext也具有国际化的功能。接下来我们着重看看MessageSource这个接口。
接口定义
public interface MessageSource { //code表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数; //当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息; //locale表示本地化对象; String getMessage(String code, Object[] args, String defaultMessage, Locale locale); //与上面的方法类似,只不过在找不到资源中对应的属性名时, //直接抛出NoSuchMessageException异常; String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException; //将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。 String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException; }
UML类图
1.我们依次分析下各个类的作用
HierarchicalMessageSource,该接口提供了设置获取父容器的方法,用于构建MessageSource体系的父子层级结构。其方法定义如下:
// 为当前MessageSource设置父MessageSource void setParentMessageSource(@Nullable MessageSource parent); // 获取当前MessageSource的父MessageSource @Nullable MessageSource getParentMessageSource();
- MessageSourceSupport,这个类的作用类似于我们之前介绍的MessageFormat,主要提供了对消息的格式化功能。从这个继承关系中我们也能看出,Spring在设计时将消息的获取以及格式化进行了分隔。而在我们实际使用到具体的实现类时,又将功能做了聚合。
- DelegatingMessageSource,将所有获取消息的请求委托给父类查找,如果父类没有就报错
- AbstractMessageSource,实现了HierarchicalMessageSource,提供了对消息的通用处理方式,方便子类对具体的消息类型实现特定的策略
- AbstractResourceBasedMessageSource,提供了对Bundle的处理方式
- ResourceBundleMessageSource,基于JDK的ResourceBundle实现,可以根据名称加载Bundle
- ReloadableResourceBundleMessageSource,提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。
- StaticMessageSource,主要用于程序测试
Spring中的简单使用
我这里直接取官网中的Demo,先看官网上的一段说明:
从上文中,我们可以得出以下几点信息:
- Spring容器在启动时会自动查找一个名称定义的messageSource的Bean(同时需要实现MessageSource接口),如果找到了,那么所有获取信息的请求都会由这个类处理。如果在当前容器中没有找到的话,会在父容器中继续查找。
- 如果没有找到,那么Spring会自己new一个DelegatingMessageSource对象,并用这个对象处理消息
基于上面的结论,我们可以做如下配置:
<!--application.xml--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean>
同时配置下面三个properties文件:
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
测试代码:
public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("application.xml"); String message1 = resources.getMessage("message", null, "Default", null); String message2 = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message1); // 输出 Alligators rock! System.out.println(message2); // 输出 The userDao argument is required. }
同时Spring对资源的加载也遵循我们在JAVA国际化中提到的规范,我们可以将上面例子中的exceptions.properties,更名为exceptions_en_GB.properties。
// 可以看出这种方式跟我们使用JAVA直接加载国际化资源没有太大差别 public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); }