spring 国际化

spring 国际化

i18n(其 来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版 物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)

参考文章

spring 国际化-i18n
SpringMVC简单实现国际化/多语言
Spring MVC之LocaleResolver(解析用户区域)

国际化处理的大致流程

1 用户请求通过某种标识进行携带(按HTTP请求头部解析区域)(按会话属性解析区域)(按Cookie解析区域),如果用户不存在这些标识,就是用默认的语言标识。
2 然后将Local语言的标识书写在Request请求中,未了方便没有Request的获取信息ThreadLocal localeContextHolder,保存与当前的请求线程相关的信息在ThreadLocal中。
3 然后根据获取Local变量从特定的文件中获取相应的信息。

配置文件

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>language/bigDataPageJsonMsg</value>   
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8" />
    </bean>
    //如果可以改变语言的信息,将语言配置写在cookie中
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
        <property name="cookieName" value="language"/>
        <property name="cookieMaxAge" value="94608000"/>
        <property name="defaultLocale" value="zh_CN" />
    </bean>

这里写图片描述
工具类-方便处理

public class I18nUtil {

    private static MessageSource messageSource;

    private final static Logger log = LoggerFactory.getLogger(I18nUtil.class);

    static {
        messageSource = AppContext.getBean("messageSource");
    }
    /**
     * 根据CODE查询,默认无通配参数,Local跟随当前cookie
     */
    public static String getMessage(String code){
        return getMessage(code,null,getLocal());
    }

    public static String getMessage(String code,Locale locale){
        return getMessage(code,null,locale);
    }

    /**
     * 根据CODE查询,自定义默认值,默认无通配参数,Local跟随当前cookie
     */
    public static String getMessage(String code,String defaultMessage){
        return getMessage(code,null,defaultMessage,getLocal());
    }

    /**
     * 根据CODE和args查询,Local跟随当前cookie
     */
    public static String getMessage(String code,Object[] args){
        return getMessage(code,args,getLocal());
    }

    public static String getMessage(String code,Object[] args,String defaultMessage,Locale locale){
        return messageSource.getMessage(code,args,defaultMessage,locale);
    }

    public static String getMessage(String code, Object[] args, Locale locale){
        try{
            return messageSource.getMessage(code,args,locale);
        }catch (Exception e){
            log.error("Query message value by key[{}] error. The reason is:"+e.getMessage(),code);
            return null;
        }

    }

    public static String getMessage(MessageSourceResolvable resolvable, Locale locale){
        try{
            return messageSource.getMessage(resolvable,locale);
        }catch (Exception e){
            log.error("Query message value error. The reason is:"+e.getMessage());
            return null;
        }

    }

    //解析用户当前的Local
    public static Locale getLocal() {
        return LocaleContextHolder.getLocale();
    }

动态的修改当前的语言

配置拦截器拦截处理改变语言的信息

   <!-- 配置LocaleChangeInterceptor 主要用于获取请求中的locale信息,将期转为Locale对像,获取LocaleResolver对象-->
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
    </mvc:interceptors>

LocaleChangeInterceptor会根据当前的lang参数动态的设置http://localhost:8080/springmvc/hello.action?lang=en_US SpringMVC简单实现国际化/多语言根据这里的代码请求的。
我们在单点登录中包含了当前国际化的标识也可以自己创建拦截器进行相应的定制处理,动态的通过LocaleResolver 动态切换语言的类型。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws ServletException {
        String newLocale = request.getParameter(getParamName());//lang=en_US
        if (newLocale != null) {
            if (checkHttpMethod(request.getMethod())) {
                LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); 
                //设置当前的请求的语言类型,并写入到cookie中去
                localeResolver.setLocale(request, response, parseLocaleValue(newLocale));

            }
        }
        return true;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
    }

    @Override
    public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
        Locale locale = null;
        TimeZone timeZone = null;
        if (localeContext != null) {
            locale = localeContext.getLocale();
            if (localeContext instanceof TimeZoneAwareLocaleContext) {
                timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
            }
            //添加到cookie中
            addCookie(response,
                    (locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
        }
        else {
            removeCookie(response);
        }
        //绑定当前的Local的类型,如果没有设置为CookieLocaleResolver默认的语言信息
        request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
                (locale != null ? locale : determineDefaultLocale(request)));
        request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
                (timeZone != null ? timeZone : determineDefaultTimeZone(request)));
    }

Spring MVC之LocaleResolver(解析用户区域)这里的描述还是比较的不错的

整个工程的执行流程

这里写图片描述
FrameworkServlet->processRequest spring处理流程

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //查看当前是否存在cookie并从中解析出Local
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        //设置到与当前线程相关的缓存中
        initContextHolders(request, localeContext, requestAttributes);
        //执行下面的流程
        doService(request, response);
    }

DispatcherServlet ->buildLocaleContext(从requst中获取到与多语言相关的信息)

protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
        if (this.localeResolver instanceof LocaleContextResolver) {
            return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request); //从当前LocaleContextResolver获取解决的Resolver
        }
        else {
            return new LocaleContext() {
                @Override
                public Locale getLocale() {
                    return localeResolver.resolveLocale(request);
                }
            };
        }
    }

CookieLocaleResolver–>resolveLocaleContext(获取多语言实现细节)

public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
        parseLocaleCookieIfNecessary(request);
        return new TimeZoneAwareLocaleContext() {
            @Override
            public Locale getLocale() {
                return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
            }
            @Override
            public TimeZone getTimeZone() {
                return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
            }
        };
    }

CookieLocaleResolver–>parseLocaleCookieIfNecessary从Cookie中查看是否有多语言的标识信息,如果没有看CookieLocaleResolver中是否设置默认的语言信息,如果没有设置就从request.getLocale();中获取当前的多语言信息。

private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
        if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
            // Retrieve and parse cookie value.
            Cookie cookie = WebUtils.getCookie(request, getCookieName());
            Locale locale = null;
            TimeZone timeZone = null;
            if (cookie != null) {
                String value = cookie.getValue();
                String localePart = value;
                String timeZonePart = null;
                int spaceIndex = localePart.indexOf(' ');
                if (spaceIndex != -1) {
                    localePart = value.substring(0, spaceIndex);
                    timeZonePart = value.substring(spaceIndex + 1);
                }
                locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
                if (timeZonePart != null) {
                    timeZone = StringUtils.parseTimeZoneString(timeZonePart);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale +
                            "'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : ""));
                }
            }
            //这样当前的用户的语言信息就和当前的Request相关了,可以冲Request中获取到Local信息。
            request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
                    (locale != null ? locale : determineDefaultLocale(request)));
            request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
                    (timeZone != null ? timeZone : determineDefaultTimeZone(request)));
        }
    }


    protected Locale determineDefaultLocale(HttpServletRequest request) {
        Locale defaultLocale = getDefaultLocale();
        // 如果CookieLocaleResolver没有设置默认的语言信息,就从Head头中获取语言的信息
        if (defaultLocale == null) {
            defaultLocale = request.getLocale();
        }
        return defaultLocale;
    }

FrameworkServlet(processRequest中调用当前方法)–>initContextHolders 将当前的LocaleContext与当前的请求线程相关联起来,方便后序请求的处理操作,请求结束后在将信息清空。LocaleContextHolder使用ThreadLocal进行处理的,具体看源码。

private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {

        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
    }

然后调用DispatcherServlet doService进行处理,处理相关的业务分发逻辑

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

之前在LocaleChangeInterceptor中修改当前Local信息并将Local写入cookie中去,就是使用了request获取到localeResolver,也就是刚刚上面所看到的信息进行了设置处理。LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
RequestContextUtils

    public static LocaleResolver getLocaleResolver(HttpServletRequest request) {
        return (LocaleResolver) request.getAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE);
    }
    localeResolver.setLocale(request, response, parseLocaleValue(newLocale));

因此获取Local可以通过两种方式
RequestContextUtils

public static Locale getLocale (HttpServletRequest request) {  
        LocaleResolver localeResolver = getLocaleResolver (request);  
        if (localeResolver != null ) {  
            return localeResolver.resolveLocale(request);  
        }  
        else {  
            return request.getLocale();  
        }  
}
CookieLocaleResolver
public Locale resolveLocale(HttpServletRequest request) {
        parseLocaleCookieIfNecessary(request);//从cookie中获取是否存在,然后解析Local,放置在Request中
        return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
    }

LocaleContextHolder

Locale locale = LocaleContextHolder.getLocale(); 

总结

多看源码,多学习处理流程,了解实现的细节问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值