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国际化消息解析原理