在阅读完 registerBeanPostProcessors 源码之后, 下一步就进入到 initMessageSource,这一步主要作用是初始化国际化文件。
这段源码是一个Java方法,用于初始化消息源(MessageSource)。在Spring框架中,消息源用于提供本地化消息,例如错误消息或用户界面文本,以便支持国际化和本地化。
让我们逐行分析这段源码:
这段源码的作用是在Spring应用程序中初始化消息源,以便支持国际化和本地化的消息处理。具体的消息源实现可能会根据应用程序的需求而有所不同。
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
// 注册一个messageSource
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}
MessageSource在官网中的描述如下图所示:
ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,它可以按层次结构解析消息。这些接口共同构成了Spring实现消息解析的基础。这些接口定义的方法包括:
String getMessage(String code, Object[] args, String default, Locale loc):从MessageSource中检索消息的基本方法。当找不到指定区域设置的消息时,将使用默认消息。传入的任何参数都将成为替换值,使用标准库提供的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作为MessageSource。如果ApplicationContext找不到任何消息源,将实例化一个空的DelegatingMessageSource以接受上述定义的方法的调用。
Spring提供了三种MessageSource实现,分别是ResourceBundleMessageSource、ReloadableResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource以实现嵌套消息。StaticMessageSource很少使用,但提供了以编程方式向源中添加消息的方法。下面是一个示例,展示了ResourceBundleMessageSource的用法:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>formatvalue>
<value>exceptionsvalue>
<value>windowsvalue>
list>
property>
bean>
beans>
该示例假设您的类路径中有三个资源包,分别称为format、exceptions和windows。通过ResourceBundle对象处理任何解析消息的请求遵循JDK标准的方式。为了示例的目的,假设上述两个资源包文件的内容如下所示:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下面的示例展示了运行MessageSource功能的程序。请记住,所有的ApplicationContext实现也是MessageSource实现,因此可以将其转换为MessageSource接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
结果如下:
Alligators rock!
总结一下,MessageSource在名为beans.xml的文件中进行了定义,该文件位于类路径的根目录下。messageSource bean定义通过其basenames属性引用了多个资源包。在basenames属性的列表中传递的三个文件作为文件存在于类路径的根目录下,分别为format.properties、exceptions.properties和windows.properties。
下面的示例展示了传递给消息查找的参数。这些参数将被转换为String对象,并插入到查找消息中的占位符中。
<beans>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
bean>
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
bean>
beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
调用execute()方法后的输出结果如下所示:
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的文件。
通常,区域设置的解析由应用程序的周围环境管理。在下面的示例中,手动指定了用于解析(英国)消息的区域设置:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
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);
}
运行上述程序后的输出结果如下所示:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用MessageSourceAware接口来获取对已定义的任何MessageSource的引用。在实现了MessageSourceAware接口的ApplicationContext中定义的任何bean在创建和配置时都会被注入应用程序上下文的MessageSource。
由于Spring的MessageSource基于Java的ResourceBundle,它不会合并具有相同基本名称的资源包,而只会使用找到的第一个资源包。后续具有相同基本名称的消息资源包将被忽略。
作为ResourceBundleMessageSource的替代方案,Spring提供了ReloadableResourceBundleMessageSource类。这个变种支持与标准的基于JDK的ResourceBundleMessageSource实现相同的资源包文件格式,但比它更灵活。特别是,它允许从任何Spring资源位置读取文件(不仅限于类路径),并支持在热加载资源包属性文件时高效地进行缓存。有关详细信息,请参阅ReloadableResourceBundleMessageSource的Java文档。