spring学习4-国际化

国际化

国际化也称作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 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.
     * 

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. *

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。


spring学习4-国际化_第1张图片
LocaleResolver实现.png

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

    /**
     * Initialize the LocaleResolver used by this class.
     * 

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。


spring学习4-国际化_第2张图片
LocaleResolver配置.png

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


spring学习4-国际化_第3张图片
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-国际化)