Spring源码学习笔记(九、Spring启动流程解析:初始化消息源)
目录:
- 什么是消息源
- 如何使用消息源
- Spring是如何实现消息源的
什么是消息源
Spring中定义一个MessageSource接口,以用于支持信息的国际化和包含参数的信息替换。
ApplicationContext接口扩展了MessageSource接口,因而提供了消息处理的功能(i18n或者国际化)。与HierarchicalMessageSource一起使用,还能够处理嵌套的消息,这些是Spring提供的处理消息的基本接口。
其定义如下:
1 public interface MessageSource { 2 // 用来从MessageSource获取消息的基本方法。如果在指定的locale中没有找到消息,则使用默认的消息 3 // args中的参数将使用标准类库中的MessageFormat来替换消息中的值 4 String getMessage(String code, Object[] args, String default, Locale loc): 5 6 // 与上一个方法相同,不同之处在于:没有指定默认值。如果没找到消息,会抛出一个NoSuchMessageException异常。 7 String getMessage(String code, Object[] args, Locale loc) throws NoSuchMessageException; 8 9 // 与上面的方法不同之处在于:将code、args等属性封装到MessageSourceResolvable中 10 String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException; 11 }
ResourceBundleMessageSource会用得更多一些。StaticMessageSource很少被使用,但能以编程的方式向源添加消息。
MessageSource类关系描述:
- HierarchicalMessageSource:MessageSource子接口,提供解析层级消息的能力。
- 该接口的目的是设置父MessageSource用于当前对象无法解析消息时,由父MessageSource尝试解析。
- DelegatingMessageSource:空的MessageSource,它将所处理转发给父MessageSource。
- 如果没有父MessageSource,它不会处理任何消息。
- 用户未指定MessageSource时,Spring默认使用该类。
- 该类的功能比较简单:将字符串和参数数组格式化为一个消息字符串。
- AbstractMessageSource:支持“配置文件”方式的抽象类,内部提供一个与区域设置无关的公共消息配置文件,消息代码为关键字。
- AbstractResourceBasedMessageSource:基于资源并实现了MessageSource接口的抽象类,同时提供了一些参数配置的方法。
- ResourceBundleMessageSource:该类通过beanName指定一个资源名(包括类路径的全限定资源名),或通过beanNames指定一组资源名。
- 不同的区域获取加载资源文件,达到国际化信息的目的。
- 该类依赖JDK的ResourceBundle,并结合了JDK MessageFormat提供的标准消息解析。
- 被访问的ResourceBundle和为每个消息生成的MessageFormat都会被缓存。
- ReloadableResourceBundleMessageSource:同ResourceBundleMessageSource。
- 该类参与Spring ApplicationContex的资源加载。
- 它使用java.util.Properties作为其消息的自定义数据结构,通过PropertiesPersister策略从Spring Resource中加载它们。
- 该策略不仅能够根据时间戳的更改重新加载文件,而且还能够使用特定的字符编码加载属性文件。
- StaticMessageSource:MessageSource的简单实现,允许以编程方式注册消息,提供基本的国际化支持(通常用于测试)
如何使用消息源
1、编写消息源(中文messageSource文件需要把中文转为Ascii)
1 # exceptions_en_US.properties文件 2 exceptions.illegal = The user {0} attempted to login, time: {1} 3 4 # exceptions_zh_CN.properties文件 5 exceptions.illegal = \u975e\u6cd5\u7528\u6237{0}\u5c1d\u8bd5\u767b\u5f55, \u65f6\u95f4\uff1a{1} 6 7 # message_en_US.properties文件 8 id.ilegal = userid {0} is illegal! time: {1} 9 password.empty = username{0}'s password is empty! 10 11 # message_zh_CN.properties文件 12 id.ilegal = \u7528\u6237\u7f16\u53f7{0}\u975e\u6cd5\u767b\u5f55\uff01{1} 13 password.empty = \u7528\u6237\u540d{0}\u7684\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a\uff01
目录如下:
2、配置ResourceBundleMessageSource
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> 7 <property name="basenames"> 8 <list> 9 <!-- 此处为消息源的目录 --> 10 <value>message.exceptions</value> 11 <value>message.message</value> 12 </list> 13 </property> 14 </bean> 15 16 </beans>
3、测试
1 public class MessageTest { 2 3 public static void main(String[] args) { 4 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("messageSource.xml"); 5 6 Object[] argArray = {"0001", new Date()}; 7 String message1 = ctx.getMessage("id.ilegal", argArray, Locale.CHINA); 8 String message2 = ctx.getMessage("id.ilegal", argArray, Locale.US); 9 System.out.println(message1); 10 System.out.println(message2); 11 12 Object[] argArray2 = {"JDR"}; 13 String message3 = ctx.getMessage("password.empty", argArray2, Locale.CHINA); 14 System.out.println(message3); 15 } 16 }
输出结果如下:
Spring是如何实现消息源的
1 protected void initMessageSource() { 2 // 获取BeanFactory 3 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); 4 5 // beanFactory中是否包含名字为messageSource的Bean 6 if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) { 7 // 创建MessageSource类型的Bean 8 this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class); 9 // 判断messageSource是否有parent MessageSource 10 if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) { 11 // 设置MessageSource的parent MessageSource. 12 HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource; 13 if (hms.getParentMessageSource() == null) { 14 // 如果未注册任何父MessageSource,则仅将父上下文的MessageSource设置为父MessageSource 15 hms.setParentMessageSource(getInternalParentMessageSource()); 16 } 17 } 18 if (logger.isDebugEnabled()) { 19 logger.debug("Using MessageSource [" + this.messageSource + "]"); 20 } 21 } 22 // beanFactory中不包含名字为messageSource的Bean 23 else { 24 // 新建DelegatingMessageSource对象 25 DelegatingMessageSource dms = new DelegatingMessageSource(); 26 // 设置DelegatingMessageSource的parent MessageSource 27 dms.setParentMessageSource(getInternalParentMessageSource()); 28 this.messageSource = dms; 29 // 向BeanFactory中注册MessageSource 30 beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); 31 if (logger.isDebugEnabled()) { 32 logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME + 33 "': using default [" + this.messageSource + "]"); 34 } 35 } 36 }
从源码中可以得出如果可以的话,消息源id尽量定义为messageSource。
该段源码主要目的:
当一个ApplicationContext被加载时,它会自动在context中查找已定义为MessageSource类型的Bean。此Bean的名称须为messageSource。
如果找到,那么所有对上述方法的调用将被委托给该Bean。否则ApplicationContext会在其父类中查找是否含有同名的Bean。如果有,就把它作为MessageSource。如果最终没有找到任何的消息源,实例化一个空的DelegatingMessageSource,使它能够接受上述方法的调用。