美文网首页
spring学习4-国际化

spring学习4-国际化

作者: 小鲍比大爷 | 来源:发表于2018-09-24 17:09 被阅读0次

    国际化

    国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化(举个例子,人们玩的电子游戏,通常可以选择多个语言版本,适应于多个国家的玩家)。通常来讲,软件中的国际化是通过配置文件来实现的,假设某个软件要支撑两种语言,那么就需要两个版本的配置文件。

    Java国际化

    Java自身是支持国际化的,java.util.Locale用于指定当前用户所属的语言环境等信息,java.util.ResourceBundle用于查找绑定对应的资源文件。
    Locale包含了language信息和country信息,Locale创建默认locale对象时使用的静态方法:

        /**
         * This method must be called only for creating the Locale.*
         * constants due to making shortcuts.
         */
        private static Locale createConstant(String lang, String country) {
            BaseLocale base = BaseLocale.createInstance(lang, country);
            return getInstance(base, null);
        }
    

    配置文件命名规则:
    basename_language_country.properties
    必须遵循以上的命名规则,java才会识别。其中,basename是必须的,语言和国家是可选的。这里存在一个优先级概念,如果同时提供了messages.properties和messages_zh_CN.propertes两个配置文件,如果提供的locale符合en_CN,那么优先查找messages_en_CN.propertes配置文件,如果没查找到,再查找messages.properties配置文件。最后,提示下,所有的配置文件必须放在classpath中,一般放在resource目录下。
    举个例子,两个配置文件内容分别如下:

    #messages.properties
    test=hello1
    #messages_en_CN.propertes
    test=hello2
    

    代码:

    //ResourceBundle.getBundle接受两个参数:basename,locale
    System.out.println(ResourceBundle.getBundle("messages",new Locale("en","CN")).getString("test"));
    

    打印结果:

    hello2
    

    以下命名的配置文件优先级从低到高:

    messages.properties
    messages_en.properties
    messages_en_CN.properties
    

    通过配置不同的Locale相关的资源文件,我们可以通过key值取到相应环境的value值,这样便做到了国际化,这种方式是非侵入式的,让我们编写代码时可以不需要考虑国际化的问题,只需要根据配置规则配置相关资源文件,在实际读取资源配置时,指定相应的locale即可。这也是约定优于配置的体现。

    Spring国际化

    spring使用MessageSource接口实现国际化。两个实现类为:
    ResourceBundleMessageSource:基于java的ResourceBundle实现了国际化,配置文件必须放在classpath下。
    ReloadableResourceBundleMessageSource:直接使用读取文件的方式实现国际化,规则跟java的相同,支持动态修改后刷新配置,避免在业务不能中断的情况下重启进程。配置文件可以放在任意目录下,指定目录后,该类会去指定目录中加载配置文件。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.MessageSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.support.ReloadableResourceBundleMessageSource;
    import org.springframework.stereotype.Component;
    
    import java.util.Locale;
    
    @Component
    public class MessageService {
    
        @Autowired
        private MessageSource messageSource;
    
        private Locale currentLocale = new Locale("en");
    
        @Bean
        public static MessageSource getMessageSource() {
            ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
            messageSource.setBasename("classpath:messages");
            messageSource.setCacheSeconds(5);
            return messageSource;
        }
    
        public String getMessage(String key) {
            return messageSource.getMessage(key, null, key, currentLocale);
        }
    }
    

    ReloadableResourceBundleMessageSource的优点在于可以不重启进程动态刷新配置,以及指定资源目录,不需要强制放在classpath下面,如果需要放在classpath中,那么只需要在basename中的资源路径上添加"classpath:",便可以在classpath中查找配置。messageSource.setCacheSeconds用于设置配置的过期时间,单位为秒,messageSource.setCacheSeconds(5)代表每5秒配置文件就会过期,再重新查询时,就会重新加载配置。
    spring的默认配置文件命名规则跟java是相同的,也都遵循约定优于配置的思路。

    Locale信息的获取

    一般来说,典型的B/S架构,用户可能在任何地点使用任何语言登陆网站,但是网站后台是部署在固定的服务器或者是云服务上的,那么如何让后端获取客户端(浏览器端)的locale信息,从而做到针对不同的客户端进行国际化呢?spring使用LocaleResolver解析用户的http请求来获取对应的locale信息。下面的代码是典型的controller,通过解析HttpServletRequest请求来获取locale信息。

    @Controller
    public class HomeController extends BaseController {
    
      @RequestMapping(value = "", method = GET)
      public String homeDefault() {
        return homePageUrl();
      }
    
      @RequestMapping(value = "/locales", method = GET)
      public ResponseEntity<OpenLmisResponse> getLocales(HttpServletRequest request) {
        messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
        return response("locales", messageService.getLocales());
      }
    
      @RequestMapping(value = "/changeLocale", method = PUT, headers = ACCEPT_JSON)
      public void changeLocale(HttpServletRequest request) {
        messageService.setCurrentLocale(RequestContextUtils.getLocale(request));
      }
    }
    
    

    RequestContextUtils.getLocale(request)实现如下。RequestContextUtils是spring提供的工具类。可以看出LocaleResolver从设计上支持多种策略。

        /**
         * Retrieve the current locale from the given request, using the
         * LocaleResolver bound to the request by the DispatcherServlet
         * (if available), falling back to the request's accept-header Locale.
         * <p>This method serves as a straightforward alternative to the standard
         * Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
         * falling back to the latter if no more specific locale has been found.
         * <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
         * which will normally be populated with the same Locale.
         * @param request current HTTP request
         * @return the current locale for the given request, either from the
         * LocaleResolver or from the plain request itself
         * @see #getLocaleResolver
         * @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
         */
        public static Locale getLocale(HttpServletRequest request) {
            LocaleResolver localeResolver = getLocaleResolver(request);
            return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
        }
    
    

    LocaleResolver包含以下四种策略实现。AcceptHeaderLocaleResolver,CookieLocaleResolver,FixedLocaleResolver和SessionLocaleResolver。


    LocaleResolver实现.png

    下面的代码是DispatcherServlet中初始化LocaleResolver的过程,可以看到LocaleResolver实际是支持用户进行配置的,如果没有配置,那么使用getDefaultStrategy方法获取默认策略。

        /**
         * Initialize the LocaleResolver used by this class.
         * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
         * we default to AcceptHeaderLocaleResolver.
         */
        private void initLocaleResolver(ApplicationContext context) {
            try {
                this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
                if (logger.isDebugEnabled()) {
                    logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default.
                this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
                if (logger.isDebugEnabled()) {
                    logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                            "': using default [" + this.localeResolver + "]");
                }
            }
        }
    

    查看项目工程代码,发现项目中配置的是CookieLocaleResolver。


    LocaleResolver配置.png

    几种LocaleResolver的用法不同,不过从名字便可以看出一二。


    accept-language.png

    AcceptHeaderLocaleResolver:直接从Http请求的Header中通过accept-language获取locale信息
    CookieLocaleResolver:从cookie中获取,如果获取不到,也是通过accept-language获取locale信息
    SessionLocaleResolver:从session中获取,如果获取不到,也是通过accept-language获取locale信息
    FixedLocaleResolver:固定locale,基本没啥用

    至此,整个后端的国际化过程已经比较清楚:后端通过解析客户端(浏览器端)传递的locale信息,将locale拿出,然后再通过spring的MessageSource类传入locale信息,取出相应的配置文件,解析出相应配置,从而便完成了国际化。

    相关文章

      网友评论

          本文标题:spring学习4-国际化

          本文链接:https://www.haomeiwen.com/subject/rkdrnftx.html