Spring 源码阅读 15:初始化 MessageSource

这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 14:注册 BeanPostProcessor

前情提要

在之前的 ApplicationContext 初始化 Spring 容器 一文中,提到 AbstractApplicationContext#refresh 方法是一个非常重要的方法,它包含了 Spring 容器初始化的整个流程。最近的一系列文章都在深入分析这个方法中的每一个步骤的具体原理,本文接着分析 MessageSource 的初始化,也就是refresh方法中的这行代码:

// Initialize message source for this context.
initMessageSource();
复制代码

首先介绍一下 MessageSource 是什么。

MessageSource 是什么

在 Spring 中,MessageSource 主要用来处理国际化的问题,也就是常说的i18n(Internationalization)。

比如,我们开发的网站要适配多个国家和地区的语言,就需要使用不同的语言展示网站的内容,这事就需要用到i18n组件,MessageSource 就是 Spring 中的i18n组件。

MessageSource 本身是一个接口,接口定义如下:

public interface MessageSource {
@Nullable
   String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}
复制代码

这三个方法都是用来通过一个指定的标识符(code)以及语言信息获取特定消息的本地化文本。MessageSource 相关的重要的实现类以及他们之间的关系如下:

这里有一个重要的 HierarchicalMessageSource 子接口,提供了分层处理消息的功能。以下是接口的定义。

public interface HierarchicalMessageSource extends MessageSource {
void setParentMessageSource(@Nullable MessageSource parent);
@Nullable
   MessageSource getParentMessageSource();
}
复制代码

简而言之,就是给 MessageSource 提供了父子关系,开发者可以用一个父级的 MessageSource 处理公共的消息内容,子级的 MessageSource 只需要处理对应模块的消息内容即可。当通过子级的 MessageSource 获取不到消息内容是,就会到父级的 MessageSource 中获取。

接下来是两个比较典型的实现类,ResourceBundleMessageSource 和 StaticMessageSource。StaticMessageSource 是一个静态的消息源,需要提前硬编码内容,因此不是很常用。比较常用的是 ResourceBundleMessageSource,它从给定路径的消息文件中加载内容,供程序获取。

初始化 MessageSource

接下来看 Spring 初始化 MessageSource 的过程,也就是 initMessageSource 的代码。

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;
      beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
      if (logger.isTraceEnabled()) {
         logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
      }
   }
}
复制代码

首先,获取到当前上下文内部的 BeanFactory,之后整个方法体都是一个if-else语句块,判断条件是 BeanFactory 中是否包含名为MESSAGE_SOURCE_BEAN_NAME的 Bean。

public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
复制代码

也就是 Spring 在容器中查找 MessageSource 时使用的是固定的名称messageSource,因此,如果要在配置文件中配置 MessageSource,需要把bean标签的id属性设置为messageSource,才能被 Spring 作为消息源。如果我们在配置文件中配置了messageSource,则会执行if语句块的逻辑,否则执行else语句块的逻辑。下面分别来说分析。

如果beanFactory中有名为messageSource的 Bean,则获取到这个 Bean 的实例,并设置为当前上下文的 MessageSource。这里还有一个判断,如果获取到的messageSource实现了 HierarchicalMessageSource 接口,且当前上下文parent属性不为空,则将parentmessageSource赋值给messageSource作为父消息源。

如果我们没有在配置文件中配置 MessageSource,那么 Spring 就会创建一个,把它赋值给当前上下文的messageSource成员变量,并注册到beanFactory中。

这里创建的消息源类型为 DelegatingMessageSource,DelegatingMessageSource 的逻辑是,直接从父消息源获取消息,或者根据提供的内容生成默认的消息,否则就返回空或者抛出异常。它的作用就是,当当前上下文的 MessageSource 中的方法被调用的时候,有实现类来响应方法的调用,仅此而已。

后续

本文介绍的这部分内容比较简单,并且 MessageSource 在实际开发当中使用被使用率并不高。后续会接着分析 Spring 容器初始化的代码。

猜你喜欢

转载自juejin.im/post/7135649446930415653