自学内容网 自学内容网

Spring MessageSource国际化原理

spring framework提供MessasgeSource来实现国际化。

MessageSource用法

准备properties文件,放在resources文件夹下面。这是默认语言和韩语的文件。

  • i18n/message.properties
  • i18n/message_ko.properties

文件里面的内容是key-value格式,使用{0}、{1}作为变量占位符:

argument.required=The {0} argument is required.

注册MessageSource Bean

spring提供了MessageSource的实现类ResourceBundleMessageSource。它从resources下查找多语言配置,并且会缓存结果。这是spring boot里MessageSource的初始化方法。

@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}

这里配置了messageSource的几个属性,简单介绍下对应的效果。

  • setBasenames:多语言文件的名字,多个名字用都好隔开。例如要使用上面两个文件,basenames要等于i18n/message。最终是基于basename+local+".properties"找到对应的多语言资源文件。
  • setDefaultEncoding:设置多语言文件里编码格式。默认是ISO-8859-1,要改成UTF-8。
  • setFallbackToSystemLocale:要获取默认Locale时,是不是要返回系统的Locale,也就是Locale.getDefault()。
  • setCacheMillis:控制资源文件文件本地缓存的过期时间,默认-1表示不过期。
  • setAlwaysUseMessageFormat:是否总是使用MessageFormat来解析多语言文本内容。
  • setUseCodeAsDefaultMessage:code不存在时,是否直接返回code值。

使用MessageSource

MessageSource接口提供了三个API:

// 设置参数和默认值
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

// 设置参数
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

// 从多个code中返回第一个找到的值
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

这里都是获取单个code的方法,没有获取数组的API。我们可以参考application.properties里数组的配置方式,增强MessageSource功能。 数组的定义格式:

argument.required[0]=The {0} argument is required. argument.required[1]=The {0} argument is required.

public List<String> getMessageList(String code, Object[] args, Locale locale) {
    List<String> result = new ArrayList<>();
    int i = 0;
    while(true) {
        val listCode = String.format("%s[%d]", code, i++);
        String message = getMessage(listCode, args, locale);
        if (message == null) {
            break;
        }
        result.add(message);
    }
    return result;
}

原理介绍

查找code的过程。

  1. 遍历basenames找到对应的资源文件
// 没有变量的情况
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;
}

// 有变量的情况,使用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;
}
  1. 获得ResourceBundle
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
ClassLoader classLoader = getBundleClassLoader();
Assert.state(classLoader != null, "No bundle ClassLoader set");

MessageSourceControl control = this.control;
if (control != null) {
try {
return ResourceBundle.getBundle(basename, locale, classLoader, control);
}
catch (UnsupportedOperationException ex) {
// ...
}
}

// Fallback: plain getBundle lookup without Control handle
return ResourceBundle.getBundle(basename, locale, classLoader);
}

  1. 回调MessageSourceControl.newBundle()加载资源文件 ResourceBundle.getBundle(basename, locale, classLoader, control)方法最终回调control.newBundle()方法来查找资源文件。 MessageSourceControl重写了newBundle(),替换了java.properties的资源查找类型。
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {

// Special handling of default encoding
if (format.equals("java.properties")) {
String bundleName = toBundleName(baseName, locale);
final String resourceName = toResourceName(bundleName, "properties");
final ClassLoader classLoader = loader;
final boolean reloadFlag = reload;
InputStream inputStream = null;
if (reloadFlag) {
URL url = classLoader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
inputStream = connection.getInputStream();
}
}
}
else {
inputStream = classLoader.getResourceAsStream(resourceName);
}
if (inputStream != null) {
String encoding = getDefaultEncoding();
if (encoding != null) {
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
return loadBundle(bundleReader);
}
}
else {
try (InputStream bundleStream = inputStream) {
return loadBundle(bundleStream);
}
}
}
else {
return null;
}
}
else {
// 将 “java.class” 格式的处理委托给标准 Control
return super.newBundle(baseName, locale, format, loader, reload);
}
}

这里resourceName的格式是{baseName}_{locale}.properties。并且通过reload参数控制,是否重新加载资源文件。 最终用loadBundle(bundleReader)返回PropertyResourceBundle对象。 PropertyResourceBundle会在初始化的时候读取reader里的内容,存到一个Map<String, Object> lookup,后面就拿多语言code去Map里找。找的顺序是先从当前lookup里查,查不到再去parent.lookup里查。

public final Object getObject(String key) {
    Object obj = handleGetObject(key);
    if (obj == null) {
        if (parent != null) {
            obj = parent.getObject(key);
        }
        if (obj == null) {
            throw new MissingResourceException("Can't find resource for bundle "
                                               +this.getClass().getName()
                                               +", key "+key,
                                               this.getClass().getName(),
                                               key);
        }
    }
    return obj;
}

spring文档:Additional Capabilities of the ApplicationContext :: Spring Framework


原文地址:https://blog.csdn.net/xsgnzb/article/details/143027954

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!