这次的内容是上图中的第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` 中
上一个步骤是初始化并注册所有的 BeanPostProcessor 到 BeanFactory 中。说白了还是在为 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());
}
}
本文暂时没有评论,来添加一个吧(●'◡'●)