Spring解析Locale的原理

1.LocaleContext

    org.springframework.context.i18n.LocaleContext是一个接口,只有一个方法getLocale(),就是用来获取当前的Locale的,下面看下整体类图。

    从类图中,我们可以看到LocaleContext有三个子类:其中TimeZoneAwareLocaleContext是一个子接口,该接口提供了一个getTimeZone()方法来获取当前时区了;SimepleLocaleContext是对LocaleContext接口的一个简单实现;主要看下SimpleTimeZoneAwareLocaleContext类,这个类继承了SimpleLocaleContext,实现了TimeZoneAwareLocaleContext接口,这也就说该类可以同时获取Locale和TimeZone,这个类也是我们常用的,看下他的实现:

public class SimpleTimeZoneAwareLocaleContext extends SimpleLocaleContext implements TimeZoneAwareLocaleContext {

    private final TimeZone timeZone;


    /**
     * Create a new SimpleTimeZoneAwareLocaleContext that exposes the specified
     * Locale and TimeZone. Every {@link #getLocale()} call will return the given
     * Locale, and every {@link #getTimeZone()} call will return the given TimeZone.
     * @param locale the Locale to expose
     * @param timeZone the TimeZone to expose
     */
    /**
     * 通过构造方法设置当前的Locale和TimeZone
     * @param locale
     * @param timeZone
     */
    public SimpleTimeZoneAwareLocaleContext(Locale locale, TimeZone timeZone) {
        super(locale);
        this.timeZone = timeZone;
    }


    public TimeZone getTimeZone() {
        return this.timeZone;
    }

    @Override
    public String toString() {
        return super.toString() + " " + (this.timeZone != null ? this.timeZone.toString() : "-");
    }

}

    整个LocaleContext的设计目的是为了保存了整个应用的Locale和TimeZone。看完下面的LocaleResolver,你就会明白LocaleContext的作用。

2.LocaleResolver

    首先看下org.springframework.web.servlet.LocaleResolver的整体类图:

    上面这个类图中有两个主要的接口:org.springframework.web.servlet.LocaleResolver和org.springframework.web.servlet.LocaleContextResolver,这两个接口的设计思想和上面的LocaleContext与TimeZoneLocaleContext是一致的。LocaleResolver接口提供了对Locale操作的两个方法:

public interface LocaleResolver {

    /**
     * Resolve the current locale via the given request.
     * Can return a default locale as fallback in any case.
     * @param request the request to resolve the locale for
     * @return the current locale (never {@code null})
     */
    /**
     * 根据当前请求解析Locale
     *
     * @param request
     * @return
     */
    Locale resolveLocale(HttpServletRequest request);

    /**
     * Set the current locale to the given one.
     * @param request the request to be used for locale modification
     * @param response the response to be used for locale modification
     * @param locale the new locale, or {@code null} to clear the locale
     * @throws UnsupportedOperationException if the LocaleResolver
     * implementation does not support dynamic changing of the locale
     */
    /**
     * 设置语言环境,该方法的本质就是调用LocaleContextResolver的setLocaleContext方法,来设置应用的Locale和TimeZone,
     * 那也就意味着如果没有实现LocaleResolverContext接口的类,就一定是不能设置Locale和TimeZone,
     * 实现了LocaleContextResolver接口的类也不一定可以设置TimeZone和Locale,如FixLocaleResolver,是根据业务需求不允许设置的
     * @param request
     * @param response
     * @param locale
     */
    void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);

}

    LocaleContextResolver提供了对LocaleContext的两个操作方法:

public interface LocaleContextResolver extends LocaleResolver {

    /**
     * Resolve the current locale context via the given request.
     * 

This is primarily intended for framework-level processing; consider using * {@link org.springframework.web.servlet.support.RequestContextUtils} or * {@link org.springframework.web.servlet.support.RequestContext} for * application-level access to the current locale and/or time zone. *

The returned context may be a * {@link org.springframework.context.i18n.TimeZoneAwareLocaleContext}, * containing a locale with associated time zone information. * Simply apply an {@code instanceof} check and downcast accordingly. *

Custom resolver implementations may also return extra settings in * the returned context, which again can be accessed through downcasting. * @param request the request to resolve the locale context for * @return the current locale context (never {@code null} * @see #resolveLocale(HttpServletRequest) * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone */ LocaleContext resolveLocaleContext(HttpServletRequest request); /** * Set the current locale context to the given one, * potentially including a locale with associated time zone information. * @param request the request to be used for locale modification * @param response the response to be used for locale modification * @param localeContext the new locale context, or {@code null} to clear the locale * @throws UnsupportedOperationException if the LocaleResolver implementation * does not support dynamic changing of the locale or time zone * @see #setLocale(HttpServletRequest, HttpServletResponse, Locale) * @see org.springframework.context.i18n.SimpleLocaleContext * @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext */ void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext); }

    从上面的类图中,我们可以看到主要有四个实现类:
    1.org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
        该实现类相当于LocaleResolver的默认实现,由于它只实现了LocaleResolver接口,因此只能解析Locale,不能设置Locale,该类在DispatcherServlet调用initLocaleResolver()方法的时候,会判断IOC容器中是由有一个叫localeResolver的Bean,如果这个Bean不存在,就会初始化该类作为默认的LocaleResolver。这个类是通过判断HTTP Header中的Accept-Language字段的值来决定当前应用的Locale和TimeZone。

      /**
     * 通过Http Header的Accept-Language字段判断Locale
     * @param request
     * @return
     */
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        }
        Locale requestLocale = request.getLocale();
        List supportedLocales = getSupportedLocales();
        if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
            return requestLocale;
        }
        Locale supportedLocale = findSupportedLocale(request, supportedLocales);
        if (supportedLocale != null) {
            return supportedLocale;
        }
        return (defaultLocale != null ? defaultLocale : requestLocale);
    }

      /**
     * 通过Http Header的Accept-Language字段判断Locale
     * @param request
     * @return
     */
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        }
        Locale requestLocale = request.getLocale();
        List supportedLocales = getSupportedLocales();
        if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
            return requestLocale;
        }
        Locale supportedLocale = findSupportedLocale(request, supportedLocales);
        if (supportedLocale != null) {
            return supportedLocale;
        }
        return (defaultLocale != null ? defaultLocale : requestLocale);
    }

    2.org.springframework.web.servlet.i18n.CookieLocaleResolver
        该类是通过应用设置的Cookie来判断当前需要的Locale的,我们只需要给定CookieName,它会自动读取对应的value,设置Locale。

      //Locale 的Cookie
    private String localeAttributeName = LOCALE_SESSION_ATTRIBUTE_NAME;
    //TimeZone 的Cookie
    private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME;

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //该方法从Cookie中获取Locale
        parseLocaleCookieIfNecessary(request);
        return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
    }

    @Override
    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);
            }
        };
    }

    @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();
            }
            addCookie(response,
                    (locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
        }
        else {
            removeCookie(response);
        }
        request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
                (locale != null ? locale : determineDefaultLocale(request)));
        request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
                (timeZone != null ? timeZone : determineDefaultTimeZone(request)));
    }

    3.org.springframework.web.servlet.i18nSessionLocaleResolver
        由名字可知,该类是通过设置Session来实现的,实现原理和CookieLocaleResolver大差不差。

        //Locale 的Session
    private String localeAttributeName = LOCALE_SESSION_ATTRIBUTE_NAME;
    //TimeZone 的Session
    private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME;

        @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //通过sssion获取
        Locale locale = (Locale) WebUtils.getSessionAttribute(request, this.localeAttributeName);
        if (locale == null) {
            locale = determineDefaultLocale(request);
        }
        return locale;
    }

    @Override
    public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
        return new TimeZoneAwareLocaleContext() {
            @Override
            public Locale getLocale() {
                Locale locale = (Locale) WebUtils.getSessionAttribute(request, localeAttributeName);
                if (locale == null) {
                    locale = determineDefaultLocale(request);
                }
                return locale;
            }
            @Override
            public TimeZone getTimeZone() {
                TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName);
                if (timeZone == null) {
                    timeZone = determineDefaultTimeZone(request);
                }
                return timeZone;
            }
        };
    }

    //setLocale方法是在抽象类:org.springframework.web.servlet.i18n.AbstractLocaleContextResolver中,调用了子类的实现,也就是该方法
    @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();
            }
        }
        WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);
        WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone);
    }

    4.org.springframework.web.servlet.i18n.FixLocaleResolver
        该类从名字就可以知道是一个固定的LocaleResolver,也就是说该类一旦设置了默认的Locale和TimeZone,就不可更改,更改会抛出异常。

private Locale defaultLocale;
private TimeZone defaultTimeZone;

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = getDefaultLocale();
        if (locale == null) {
            locale = Locale.getDefault();
        }
        return locale;
    }

    @Override
    public LocaleContext resolveLocaleContext(HttpServletRequest request) {
        return new TimeZoneAwareLocaleContext() {
            @Override
            public Locale getLocale() {
                return getDefaultLocale();
            }
            @Override
            public TimeZone getTimeZone() {
                return getDefaultTimeZone();
            }
        };
    }

    /**
     * 更改直接抛出异常
     * @param request the request to be used for locale modification
     * @param response the response to be used for locale modification
     * @param localeContext the new locale context, or {@code null} to clear the locale
     */
    @Override
    public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
        throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
    }

    总结一下,常用的是CookieLocaleResolver和SessionLocaleResolver。LocaleResolver的初始化是在DispatcherServlet的initLocaleResolver方法中进行的。
无论是使用哪个实现类,Bean的id一定要申明为localeResolver,否则DIspatcherServlet读取不到,将会初始化默认的AcceptHeaderLocaleResolver。

    其他相关文章:
        java原生国际化
        Spring国际化使用教程
        Spring国际化消息解析原理

你可能感兴趣的:(Spring解析Locale的原理)