专业的编程技术博客社区

网站首页 > 博客文章 正文

Spring容器刷新—07-initMessageSource

baijin 2024-08-12 13:38:10 博客文章 7 ℃ 0 评论

这次的内容是上图中的第07步。

前言

先来看看前面几篇文章都做了什么:

`01-prepareRefresh()`: `earlyApplicationListeners`、`applicationListeners` 、`initPropertySources()`、`validateRequiredProperties`
`02-obtainFreshBeanFactory()`: `refreshBeanFactory`
`03-prepareBeanFactory()`: `ignoreDependencyInterface`、`setBeanExpressionResolver`、`registerResolvableDependency`、`Environment`
`04-postProcessBeanFactory()`: 具体实现类的特殊逻辑(`Servlet` 等)
`05-invokeBeanFactoryPostProcessors()`: 可能发生的对 `BeanFactory/BeanDefinitionRegistry` 的修改操作
`06-registerBeanPostProcessors()`: 所有的 `BeanPostProcessor` 已经被实例化并且注册到了 `BeanFactory` 中

上一个步骤是初始化并注册所有的 BeanPostProcessorBeanFactory 中。说白了还是在为 Bean 的创建做准备工作。

本文要介绍的 initMessageSource() 依然是在给 Bean 的创建做准备工作。

相比于前几篇文章的内容,本文的内容非常非常简单。

initMessageSource() 就是初始化 MessageSource,那么什么是 MessageSource 呢?

MessageSource

作用

就是用来处理国际化(i18n)资源的。接口声明如下:

public interface MessageSource {

    /**
     * Try to resolve the message. Return default message if no message was found.
     * @param code the message code to look up, e.g. 'calculator.noRateSet'.
     * MessageSource users are encouraged to base message names on qualified class
     * or package names, avoiding potential conflicts and ensuring maximum clarity.
     * @param args an array of arguments that will be filled in for params within
     * the message (params look like "{0}", "{1,date}", "{2,time}" within a message),
     * or {@code null} if none
     * @param defaultMessage a default message to return if the lookup fails
     * @param locale the locale in which to do the lookup
     * @return the resolved message if the lookup was successful, otherwise
     * the default message passed as a parameter (which may be {@code null})
     * @see #getMessage(MessageSourceResolvable, Locale)
     * @see java.text.MessageFormat
     */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    /**
     * Try to resolve the message. Treat as an error if the message can't be found.
     * @param code the message code to look up, e.g. 'calculator.noRateSet'.
     * MessageSource users are encouraged to base message names on qualified class
     * or package names, avoiding potential conflicts and ensuring maximum clarity.
     * @param args an array of arguments that will be filled in for params within
     * the message (params look like "{0}", "{1,date}", "{2,time}" within a message),
     * or {@code null} if none
     * @param locale the locale in which to do the lookup
     * @return the resolved message (never {@code null})
     * @throws NoSuchMessageException if no corresponding message was found
     * @see #getMessage(MessageSourceResolvable, Locale)
     * @see java.text.MessageFormat
     */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    /**
     * Try to resolve the message using all the attributes contained within the
     * {@code MessageSourceResolvable} argument that was passed in.
     * <p>NOTE: We must throw a {@code NoSuchMessageException} on this method
     * since at the time of calling this method we aren't able to determine if the
     * {@code defaultMessage} property of the resolvable is {@code null} or not.
     * @param resolvable the value object storing attributes required to resolve a message
     * (may include a default message)
     * @param locale the locale in which to do the lookup
     * @return the resolved message (never {@code null} since even a
     * {@code MessageSourceResolvable}-provided default message needs to be non-null)
     * @throws NoSuchMessageException if no corresponding message was found
     * (and no default message was provided by the {@code MessageSourceResolvable})
     * @see MessageSourceResolvable#getCodes()
     * @see MessageSourceResolvable#getArguments()
     * @see MessageSourceResolvable#getDefaultMessage()
     * @see java.text.MessageFormat
     */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

示例

这里大概演示一下 MessageSource 的用法。

类路径 下新增一个目录用来存放资源文件(这里新建了一个名为 message-dir 的目录),然后再该目录下新增几个不同语言后缀的配置文件。

message-dir
├── config_en.properties
└── config_zh.properties
  • config_zh.properties
user.name=\u5f20\u4e09\u4e30
user.desc=\u8d2b\u9053 {0}
  • config_en.properties
user.name=ZhangSanFeng
user.desc=I am {0}
  • 测试
public class MessageSourceTest {
    @Test
    void testMessageSource() {
        MessageSource messageSource = createMessageSource();

        String usernameZh = messageSource.getMessage("user.name", null, Locale.CHINA);
        String usernameEn = messageSource.getMessage("user.name", null, Locale.ENGLISH);

        System.out.println(usernameZh); // 张三丰
        System.out.println(usernameEn); // ZhangSanFeng

        System.out.println(messageSource.getMessage("user.desc", new String[]{usernameZh}, Locale.CHINA)); // 贫道 张三丰
        System.out.println(messageSource.getMessage("user.desc", new String[]{usernameEn}, Locale.ENGLISH)); // I am ZhangSanFeng
    }

    private static ResourceBundleMessageSource createMessageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        // 读取资源文件 message-dir 目录下的 config_*.properties 文件
        messageSource.setBasename("message-dir/config");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

简单来说,就是根据不同的语言返回不同内容。

源码流程

现在看看 initMessageSource() 源码,其实就是初始化 messageSource 属性。没什么复杂的。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
    @Nullable
    private MessageSource messageSource;

    protected void initMessageSource() {
        // getBeanFactory() 方法前面文章(02-obtainFreshBeanFactory)已经介绍过了,不同的子类都重写过该方法
        // 不过大都是返回的 `DefaultListableBeanFactory` 类型的实例
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 1. 如果当前 BeanFactory 自己已经有了名为 `messageSource` 的 Bean
        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
            // 当前 ApplicationContext 的 `messageSource` 初始化
            this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            // Make MessageSource aware of parent MessageSource.
            // 如果当前 MessageSource 是支持分层解析的
            // 就为其设置 parentMessageSource
            // parentMessageSource的获取逻辑看下面的 getInternalParentMessageSource()
            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 + "]");
            }
        }
        // 2. 如果当前 BeanFactory 中还没有名为 `messageSource` 的 Bean
        else {
            // Use empty MessageSource to be able to accept getMessage calls.
            // 2.1 创建一个新的 MessageSource
            DelegatingMessageSource dms = new DelegatingMessageSource();
            dms.setParentMessageSource(getInternalParentMessageSource());
            this.messageSource = dms;
            // 2.2 将刚刚新创建的 MessageSource 注册到 BeanFactory 中
            beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
            }
        }
    }

    @Nullable
    protected MessageSource getInternalParentMessageSource() {
        // 如果当前 ApplicationContext 的父 ApplicationContext 也是 `AbstractApplicationContext` 类型
        // 就返回 `AbstractApplicationContext.messageSource`
        return (getParent() instanceof AbstractApplicationContext ?
                ((AbstractApplicationContext) getParent()).messageSource
                // 否则就直接返回 父 ApplicationContext
                // 因为 ApplicationContext 实际上也是实现了 `MessageSource` 接口的
                : getParent());
    }
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表