ApplicationContext 接口扩展了一个名为 MessageSource 的接口,因此,它提供了国际化(「i18n」)功能。Spring 还提供了 HierarchicalMessageSource 接口,它可以分层次地解析消息。这些接口共同提供了Spring 实现消息解析的基础。在这些接口上定义的方法包括:

    • String getMessage(String code, Object[] args, String default, Locale loc):用于从 MessageSource 中获取消息的基本方法。当没有为指定的 locale 找到消息时,会使用默认的消息。任何传入的参数都成为替换值,使用标准库提供的 MessageFormat 功能。
    • String getMessage(String code, Object[] args, Locale loc):基本上与前一个方法相同,但有一点不同。不能指定默认消息。如果找不到消息,会抛出一个 NoSuchMessageException。
    • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面的方法中使用的所有属性也被包装在一个名为 MessageSourceResolvable 的类中,你可以使用这个方法。

    当 ApplicationContext 被加载时,它会自动搜索定义在上下文中的 MessageSource bean。这个 Bean 必须有 messageSource 这个名字。如果找到了这样的 Bean,所有对前面的方法的调用都被委托给消息源。如果没有找到消息源,ApplicationContext 会尝试找到一个包含有相同名称的 Bean 的父类。如果找到了,它就使用该 bean 作为消息源。如果 ApplicationContext 不能找到任何消息源,那么就会实例化一个空的 DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

    Spring 提供了三种 MessageSource 实现:ResourceBundleMessageSource、ReloadableResourceBundleMessageSource 和 StaticMessageSource。它们都实现了 HierarchicalMessageSource,以便进行嵌套消息传递。StaticMessageSource 很少被使用,但它提供了向消息源添加消息的程序化方法。下面的例子显示了 ResourceBundleMessageSource:

    1. <beans>
    2. <bean id="messageSource"
    3. class="org.springframework.context.support.ResourceBundleMessageSource">
    4. <property name="basenames">
    5. <list>
    6. <value>format</value>
    7. <value>exceptions</value>
    8. <value>windows</value>
    9. </list>
    10. </property>
    11. </bean>
    12. </beans>

    这个例子假设你在 classpath 中定义了三个资源包,分别是 format、exceptions 和 windows。任何解析消息的请求都以 JDK 标准的方式处理,即通过 ResourceBundle 对象解析消息。就本例而言,假设上述两个资源包文件的内容如下:

    1. # in format.properties
    2. message=Alligators rock!
    1. # in exceptions.properties
    2. argument.required=The {0} argument is required.

    下一个例子显示了一个运行 MessageSource 功能的程序。请记住,所有 ApplicationContext 的实现也是 MessageSource 的实现,所以可以投到 MessageSource 接口:

    1. public static void main(String[] args) {
    2. MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    3. String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    4. System.out.println(message);
    5. }

    上述程序的结果输出如下:

    1. Alligators rock!

    简而言之,MessageSource 被定义在一个叫做 beans.xml 的文件中,它存在于你的 classpath 的根部。messageSource Bean 定义通过它的 basenames 属性引用了一些资源包。在列表中传递给 basenames 属性的三个文件作为文件存在于你的 classpath 根部,分别被称为 format.properties、exceptions.properties 和 windows.properties。

    下一个例子显示了传递给消息查询的参数。这些参数被转换为 String 对象,并被插入到查找消息的占位符中:

    1. <beans>
    2. <!-- 这个 MessageSource 被用在一个 Web 应用程序中 -->
    3. <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    4. <property name="basename" value="exceptions"/>
    5. </bean>
    6. <!-- 让我们把上述的 MessageSource 注入这个 POJO 中 -->
    7. <bean id="example" class="com.something.Example">
    8. <property name="messages" ref="messageSource"/>
    9. </bean>
    10. </beans>
    1. public class Example {
    2. private MessageSource messages;
    3. public void setMessages(MessageSource messages) {
    4. this.messages = messages;
    5. }
    6. public void execute() {
    7. String message = this.messages.getMessage("argument.required",
    8. new Object [] {"userDao"}, "Required", Locale.ENGLISH);
    9. System.out.println(message);
    10. }
    11. }

    execute 方法执行后输出如下

    1. The userDao argument is required.

    关于国际化(「i18n」),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的区域划分和回退规则。简而言之,继续使用之前定义的 messageSource 示例,如果你想根据英国(en-GB)地区设置来解析消息,你将创建名为 format_en_GB.properties、exceptions_en_GB.properties 和windows_en_GB.properties 的文件。

    通常情况下,地域性解析是由应用程序的周围环境管理的。在下面的例子中,(英国)信息所依据的地方语言是手动指定的:

    1. # in exceptions_en_GB.properties
    2. argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
    1. public static void main(final String[] args) {
    2. MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    3. String message = resources.getMessage("argument.required",
    4. new Object [] {"userDao"}, "Required", Locale.UK);
    5. System.out.println(message);
    6. }

    运行上述程序的结果输出如下:

    1. Ebagum lad, the 'userDao' argument is required, I say, required.

    你也可以使用 MessageSourceAware 接口来获取对任何已定义的 MessageSource 的引用。任何定义在实现 MessageSourceAware 接口的 ApplicationContext 中的 Bean,在 Bean 被创建和配置时都会被注入 ApplicationContext 的 MessageSource。

    :::info 因为 Spring 的 MessageSource 是基于 Java 的 ResourceBundle,它不会合并具有相同基名的捆绑包,而是 只使用找到的第一个捆绑包。随后的具有相同基名的消息包会被忽略。 ::: :::info 作为 ResourceBundleMessageSource 的替代品,Spring 提供了一个 ReloadableResourceBundleMessageSource 类。这个变体支持相同的捆绑文件格式,但比基于 JDK 的标准 ResourceBundleMessageSource 实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅仅是 classpath)读取文件,并支持捆绑属性文件的热重载(同时在两者之间有效地缓存它们)。详情见ReloadableResourceBundleMessageSource javadoc。 :::