Spring boot国际化

国际化主要是引入了MessageSource,我们简单看下如何使用,以及其原理。

1.1 设置资源文件

在 properties新建i18n目录

新建message文件:

messages.properties

error.title=Your request cannot be processed

messages_zh_CN.properties

error.title=您的请求无法处理

1.2 配置

修改properties文件的目录:在application.yml或者application.properties中配置 spring.message.basename

spring:
    application:
        name: test-worklog
    messages:
        basename: i18n/messages
        encoding: UTF-8

1.3 使用

引用自动注解的MessageSource,调用messageSource.getMessage即可,注意,需要通过LocaleContextHolder.getLocale()获取当前的地区。

@Autowired
private MessageSource messageSource;
/**
 * 国际化
 *
 * @param result
 * @return
 */
public String getMessage(String result, Object[] params) {
    String message = "";
    try {
        Locale locale = LocaleContextHolder.getLocale();
        message = messageSource.getMessage(result, params, locale);
    } catch (Exception e) {
        LOGGER.error("parse message error! ", e);
    }
    return message;
}

如何设置个性化的地区呢? forLanguageTag 即可

 Locale locale = Locale.forLanguageTag(user.getLangKey());

1.4 原理分析

MessageSourceAutoConfiguration中,实现了autoconfig

@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {

该类一方面读取配置文件,一方面创建了MessageSource的实例:

@Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(this.basename)) {
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
                    StringUtils.trimAllWhitespace(this.basename)));
        }
        if (this.encoding != null) {
            messageSource.setDefaultEncoding(this.encoding.name());
        }
        messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
        messageSource.setCacheSeconds(this.cacheSeconds);
        messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
        return messageSource;
    }

因此,默认是加载的ResourceBundleMessageSource,该类派生与于AbstractResourceBasedMessageSource

@Override
    public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
        String msg = getMessageInternal(code, args, locale);
        if (msg != null) {
            return msg;
        }
        if (defaultMessage == null) {
            String fallback = getDefaultMessage(code);
            if (fallback != null) {
                return fallback;
            }
        }
        return renderDefaultMessage(defaultMessage, args, locale);
    }

最终是调用resolveCode来获取message,通过ResourceBundle来获取message

    @Override
    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;
    }
    
// 获取ResourceBundle 
protected ResourceBundle getResourceBundle(String basename, Locale locale) {
        if (getCacheMillis() >= 0) {
            // Fresh ResourceBundle.getBundle call in order to let ResourceBundle
            // do its native caching, at the expense of more extensive lookup steps.
            return doGetBundle(basename, locale);
        }
        else {
            // Cache forever: prefer locale cache over repeated getBundle calls.
            synchronized (this.cachedResourceBundles) {
                Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
                if (localeMap != null) {
                    ResourceBundle bundle = localeMap.get(locale);
                    if (bundle != null) {
                        return bundle;
                    }
                }
                try {
                    ResourceBundle bundle = doGetBundle(basename, locale);
                    if (localeMap == null) {
                        localeMap = new HashMap<Locale, ResourceBundle>();
                        this.cachedResourceBundles.put(basename, localeMap);
                    }
                    localeMap.put(locale, bundle);
                    return bundle;
                }
                catch (MissingResourceException ex) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
                    }
                    // Assume bundle not found
                    // -> do NOT throw the exception to allow for checking parent message source.
                    return null;
                }
            }
        }
    }

//  ResourceBundle  
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, getBundleClassLoader(), new MessageSourceControl());
}

最后来看getMessageFormat:

/**
     * Return a MessageFormat for the given bundle and code,
     * fetching already generated MessageFormats from the cache.
     * @param bundle the ResourceBundle to work on
     * @param code the message code to retrieve
     * @param locale the Locale to use to build the MessageFormat
     * @return the resulting MessageFormat, or {@code null} if no message
     * defined for the given code
     * @throws MissingResourceException if thrown by the ResourceBundle
     */
    protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
            throws MissingResourceException {

        synchronized (this.cachedBundleMessageFormats) {
            // 从缓存读取
            Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
            Map<Locale, MessageFormat> localeMap = null;
            if (codeMap != null) {
                localeMap = codeMap.get(code);
                if (localeMap != null) {
                    MessageFormat result = localeMap.get(locale);
                    if (result != null) {
                        return result;
                    }
                }
            }
            // 缓存miss,从bundle读取
            String msg = getStringOrNull(bundle, code);
            if (msg != null) {
                if (codeMap == null) {
                    codeMap = new HashMap<String, Map<Locale, MessageFormat>>();
                    this.cachedBundleMessageFormats.put(bundle, codeMap);
                }
                if (localeMap == null) {
                    localeMap = new HashMap<Locale, MessageFormat>();
                    codeMap.put(code, localeMap);
                }
                MessageFormat result = createMessageFormat(msg, locale);
                localeMap.put(locale, result);
                return result;
            }

            return null;
        }
    }

作者:Jadepeng 出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi 您的支持是对博主最大的鼓励,感谢您的认真阅读。 本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏lzj_learn_note

阿里ARouter使用及源码解析(一)

在app的开发中,页面之间的相互跳转是最基本常用的功能。在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生...

20620
来自专栏腾讯Bugly的专栏

深入源码探索 ReactNative 通信机制

本文从源码角度剖析 ReactNative 中 Java <> Js 的通信机制(基于最新的 ReactNative for Android Release 2...

34490
来自专栏Java架构师历程

JDBC简介

简单地说,就是用于执行SQL语句的一类Java API,通过JDBC使得我们可以直接使用Java编程来对关系数据库进行操作。通过封装,可以使开发人员使用纯Jav...

11220
来自专栏别先生

struts2使用拦截器完成登陆显示用户信息操作和Struts2的国际化

  其实学习框架,就是为了可以很好的很快的完成我们的需求,而学习struts2只是为了替代之前用的servlet这一层,框架使开发更加简单,所以作为一个小菜鸟,...

21470
来自专栏java 成神之路

Spring 多线程下注入bean问题

40450
来自专栏电光石火

ssm整合Redis

这次谈谈Redis,关于Redis应该很多朋友就算没有用过也听过,算是这几年最流行的NoSql之一了。  Redis的应用场景非常多这里就不一一列举了,这次...

39980
来自专栏我的小碗汤

加载国际化文件的几种姿势

利用ResourceBundle加载国际化文件,这里列出四个方法,分别是利用默认Locale、zh_CN、en_US以及带占位符的处理方式。这里需要注意的是Ba...

15650
来自专栏不会写文章的程序员不是好厨师

Spring源码初探-IOC(5)-ApplicationContext功能扩展及其扩展点

前面几篇关于Spring的文章简单阐述了使用BeanFactory作为容器时bean的初始化过程。然而在实际使用中,我们并不会直接接触和编码BeanFactor...

13020
来自专栏Java成神之路

Java企业微信开发_05_消息推送之被动回复消息

微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可。

38720
来自专栏Ryan Miao

使用dropwizard(6)-国际化-easy-i18n

前言 Dropwizard官方文档并没有提供国际化的模块,所以只能自己加。Spring的MessageResource用的很顺手,所以copy过来。 Easy...

400120

扫码关注云+社区

领取腾讯云代金券