【Spring 源码】ApplicationContext 容器的加载原理(四)
文章目录
一、初始化 Spring 国际化消息资源
在平常使用的时候,如果需要给使用不同语言的用户设定不同的语言消息,比如设置专门的中文、英文界面。这个时候就不能直接对界面上的各种文字进行硬编码,而是每次选择不同语言的时候,分别加载不同的语言文件,这样才能方便系统的扩展,以及维护。
1. Spring 国际化例子
- 目录结构:
|-- src.main
|-- java.com.test
|-- Main.java
|-- resources
|-- message.properties
|-- message_zh_CN.properties
|-- MyBean.xml
- MyBean.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>message</value>
</list>
</property>
</bean>
</beans>
- Main.java:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("MyBean.xml");
String message1 = context.getMessage("test1", null, Locale.ROOT);
String message2 = context.getMessage("test1", null, Locale.CHINA);
System.out.println(message1);
System.out.println(message2);
}
}
- message.properties 和 message_zh_CN.properties:
# message.properties
test1=test
# message_zh_CN.properties
test1=测试文件
- 结果:
test
测试文件
2. 初始化源码
【1】获取用户配置的 MessageSource
主要代码:initMessageSource()
MessageSource 的初始化比较简单,就是查找配置文件中配置的 messageSource 类型的 Bean。并且如果没有找到用户配置的 MessageSource 的话,则会使用 DelegatingMessageSource 作为默认。
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// 处理 HierarchicalMessageSource 类型的 messageSource
if (this.parent != null
&& this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// 设置 parent
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
// 日志追踪
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// 如果配置文件中没有注册 messageSource,则使用默认的 messageSource 处理
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 + "]");
}
}
}
【2】获取 message
主要代码:getMessage()
比较重要的流程则是 context.getMessage()
,等待获取到 messageSource 以后,就可以通过容器获取对应的消息,比如 context.getMessage("test1", null, Locale.CHINA)
可以获取到 message_zh_CN.properties 文件中配置的内容。
@Override
public final String getMessage(
String code,
@Nullable Object[] args,
Locale locale
) throws NoSuchMessageException {
String msg = getMessageInternal(code, args, locale);
if (msg != null) {
return msg;
}
String fallback = getDefaultMessage(code);
if (fallback != null) {
eturn fallback;
}
throw new NoSuchMessageException(code, locale);
}
@Nullable
protected String getMessageInternal(
@Nullable String code,
@Nullable Object[] args,
@Nullable Locale locale
) {
if (code == null) {
return null;
}
if (locale == null) {
locale = Locale.getDefault();
}
Object[] argsToUse = args;
// 如果 args 参数没有值,则使用 resolveCodeWithoutArguments 查找
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
String message = resolveCodeWithoutArguments(code, locale);
if (message != null) {
return message;
}
}
else {
// 处理传入的参数
argsToUse = resolveArguments(args, locale);
// 通过子类处理消息,将传入的参数填入占位符中
MessageFormat messageFormat = resolveCode(code, locale);
if (messageFormat != null) {
synchronized (messageFormat) {
return messageFormat.format(argsToUse);
}
}
}
// 如果没有处理成功,会创建一个默认的 MessageFormat 处理
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}
// 如果没有找到相关的消息,则查找父级的
return getMessageFromParent(code, argsToUse, locale);
}
【3】不带参数获取值
主要代码:resolveCodeWithoutArguments()
获取内容的主要方法在 AbstractMessageSource 的 getMessageInternal()
中的 resolveCodeWithoutArguments()
和 resolveCode()
来获取配置资源。因为在配置文件中指定了 ResourceBundleMessageSource 作为目标调用类,而且目标类中重写了这两个方法,所以最终调用的也是这两个方法。
protected String resolveCodeWithoutArguments(String code, Locale locale) {
Set<String> basenames = getBasenameSet();
for (String basename : basenames) {
// 获取配置资源
ResourceBundle bundle = getResourceBundle(basename, locale);
if(bundle != null) {
// 获取值或空值
String result = getStringOrNull(bundle, code);
if (result != null) {
return result;
}
}
}
return null;
}
【4】处理消息并返回 MessageFormat
主要代码:resolveCodeWithoutArguments()
在方法中获取的 MessageFormat 主要用来处理消息和传入的参数。
protected MessageFormat resolveCode(String code, Locale locale) {
Set<String> basenames = getBasenameSet();
for (String basename : basenames) {
// 获取配置资源
ResourceBundle bundle = getResourceBundle(basename, locale);
if (bundle != null) {
// 获取值,并且将参数填入占位符中
MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
if (messageFormat != null) {
return messageFormat;
}
}
}
return null;
}
【5】获取消息处理器
主要代码:getBundleImpl()
private static ResourceBundle getBundleImpl(
String baseName,
Locale locale,
ClassLoader loader,
Control control
) {
if (locale == null || control == null) {
throw new NullPointerException();
}
CacheKey cacheKey = new CacheKey(baseName, locale, loader);
ResourceBundle bundle = null;
// 查找缓存
BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef != null) {
bundle = bundleRef.get();
bundleRef = null;
}
// 如果在缓存中找到,则直接返回
if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
return bundle;
}
boolean isKnownControl = (control == Control.INSTANCE) ||
(control instanceof SingleFormatControl);
List<String> formats = control.getFormats(baseName);
if (!isKnownControl && !checkList(formats)) {
throw new IllegalArgumentException("Invalid Control: getFormats");
}
// 循环查找,直到找到目标文件返回
ResourceBundle baseBundle = null;
for (Locale targetLocale = locale; targetLocale != null;
targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
if (!isKnownControl && !checkList(candidateLocales)) {
throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
}
// 通过递归查找配置资源
bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
// 判断获取的配置文件后缀名中是否设置了具体的国别
// 并且如果配置文件后缀中没有配置国别,则会继续循环
if (isValidBundle(bundle)) {
boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
if (!isBaseBundle
|| bundle.locale.equals(locale)
|| (candidateLocales.size() == 1
&& bundle.locale.equals(candidateLocales.get(0)))) {
break;
}
if (isBaseBundle && baseBundle == null) {
baseBundle = bundle;
}
}
}
// 没有找到则会抛出异常
if (bundle == null) {
if (baseBundle == null) {
throwMissingResourceException(baseName, locale, cacheKey.getCause());
}
bundle = baseBundle;
}
keepAlive(loader);
return bundle;
}