# 精尽 Spring MVC 源码解析 —— LocaleResolver 组件 > LocaleResolver 实际使用很少,所以艿艿也没特别细看,就看了下 AcceptHeaderLocaleResolver 类。 > > 这里艿艿就转载 N3verL4nd 的 [《SpringMVC 源码解析 —— LocaleResolver》](https://blog.csdn.net/x_iya/article/details/78285318) 。 # 1. LocaleResolver LocaleResolver 是一个解决国际化的策略接口。它包含两个方法:`#resolveLocale(...)` 和 `#setLocale(...)` ,代码如下: ``` public interface LocaleResolver { Locale resolveLocale(HttpServletRequest request); void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale); } ``` 实现此接口的类: [![LocaleResolver 类图](21-Spring MVC 源码解析-LocaleResolver 组件.assets/47659f045806606287c2d8d3a44f295c.png)](http://static.iocoder.cn/47659f045806606287c2d8d3a44f295c)LocaleResolver 类图 # 2. AcceptHeaderLocaleResolver SpringMVC 默认使用的是:AcceptHeaderLocaleResolver 。 [![`DispatcherServlet.properties`](21-Spring MVC 源码解析-LocaleResolver 组件.assets/41a00a9f69243a72aa4c82f6bb758b05.png)](http://static.iocoder.cn/41a00a9f69243a72aa4c82f6bb758b05)`DispatcherServlet.properties` ``` /** * LocaleResolver 接口的实现类 * 简单地使用 HTTP 请求头里的 Accept-Language 来指定 Locale对象(即客户端浏览器发送的语言环境,通常是客户端的操作系统) * * 注意:不支持 setLocale 方法,因为只能通过更改客户端的区域设置来更改 Accept-Language 请求头 */ public class AcceptHeaderLocaleResolver implements LocaleResolver { private final List supportedLocales = new ArrayList<>(4); @Nullable private Locale defaultLocale; /** * 配置支持的区域设置列表 */ public void setSupportedLocales(@Nullable List locales) { this.supportedLocales.clear(); if (locales != null) { this.supportedLocales.addAll(locales); } } /** * 返回配置的支持的区域设置列表 */ public List getSupportedLocales() { return this.supportedLocales; } /** * 如果 HTTP 请求头没有 Accept-Language,则使用该默认的语言环境设置 */ public void setDefaultLocale(@Nullable Locale defaultLocale) { this.defaultLocale = defaultLocale; } /** * 返回默认配置的语言环境(如果有) */ @Nullable public Locale getDefaultLocale() { return this.defaultLocale; } @Override public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } Locale requestLocale = request.getLocale(); if (isSupportedLocale(requestLocale)) { return requestLocale; } Locale supportedLocale = findSupportedLocale(request); if (supportedLocale != null) { return supportedLocale; } return (defaultLocale != null ? defaultLocale : requestLocale); } private boolean isSupportedLocale(Locale locale) { List supportedLocales = getSupportedLocales(); return (supportedLocales.isEmpty() || supportedLocales.contains(locale)); } @Nullable private Locale findSupportedLocale(HttpServletRequest request) { Enumeration requestLocales = request.getLocales(); while (requestLocales.hasMoreElements()) { Locale locale = requestLocales.nextElement(); if (getSupportedLocales().contains(locale)) { return locale; } } return null; } @Override public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { throw new UnsupportedOperationException( "Cannot change HTTP accept header - use a different locale resolution strategy"); } } ``` `#resolveLocale(...)` 方法的处理流程: - 如果 HTTP 请求头里不含有 `Accept-Language` ,并且默认的语言环境 `defaultLocale` 不为空,则使用默认的语言环境。否则,从请求里获得 Locale 。 - 而一般的我们没有配置 `supportedLocales` 与`defaultLocale` 属性(需要配置注入),所以 AcceptHeaderLocaleResolver 使用`Accept-Language` 来构造 Locale 对象。 # 3. AbstractLocaleResolver ``` /** * 实现 LocaleResolver 接口的抽象基类 * 提供对默认语言环境的支持 */ public abstract class AbstractLocaleResolver implements LocaleResolver { @Nullable private Locale defaultLocale; public void setDefaultLocale(@Nullable Locale defaultLocale) { this.defaultLocale = defaultLocale; } @Nullable protected Locale getDefaultLocale() { return this.defaultLocale; } } ``` # 4. LocaleContextResolver ``` public interface LocaleContextResolver extends LocaleResolver { LocaleContext resolveLocaleContext(HttpServletRequest request); void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable LocaleContext localeContext); } ``` # 5. AbstractLocaleContextResolver ``` /** * LocaleContextResolver 实现的抽象基类:提供对默认语言环境和默认时区的支持 * 还提供了 resolveLocale 和 setLocale 的预实现版本,委托给 resolveLocaleContext 和 setLocaleContext */ public abstract class AbstractLocaleContextResolver extends AbstractLocaleResolver implements LocaleContextResolver { @Nullable private TimeZone defaultTimeZone; public void setDefaultTimeZone(@Nullable TimeZone defaultTimeZone) { this.defaultTimeZone = defaultTimeZone; } @Nullable public TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } @Override public Locale resolveLocale(HttpServletRequest request) { Locale locale = resolveLocaleContext(request).getLocale(); return (locale != null ? locale : request.getLocale()); } @Override public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null)); } } ``` # 6. SessionLocaleResolver ``` public class SessionLocaleResolver extends AbstractLocaleContextResolver { public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE"; public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE"; private String localeAttributeName = LOCALE_SESSION_ATTRIBUTE_NAME; private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME; /** * 在 HttpSession 中指定相应属性的名称,保存当前的 Locale 值 * 默认值为 LOCALE_SESSION_ATTRIBUTE_NAME */ public void setLocaleAttributeName(String localeAttributeName) { this.localeAttributeName = localeAttributeName; } /** * 在 HttpSession 中指定相应属性的名称,保存当前的 TimeZone 值 * 默认值为 TIME_ZONE_SESSION_ATTRIBUTE_NAME */ public void setTimeZoneAttributeName(String timeZoneAttributeName) { this.timeZoneAttributeName = timeZoneAttributeName; } @Override public Locale resolveLocale(HttpServletRequest request) { 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 @Nullable public TimeZone getTimeZone() { TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName); if (timeZone == null) { timeZone = determineDefaultTimeZone(request); } return timeZone; } }; } @Override public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable LocaleContext localeContext) { Locale locale = null; TimeZone timeZone = null; if (localeContext != null) { locale = localeContext.getLocale(); if (localeContext instanceof TimeZoneAwareLocaleContext) { timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); } } // 将 Locale 与 TimeZone 保存到 HttpSession WebUtils.setSessionAttribute(request, this.localeAttributeName, locale); WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone); } /** * 确定给定请求的默认语言环境,如果没有找到 Locale 会话属性,则调用 * 默认实现返回指定的默认语言环境(如果有的话)返回到请求的Accept-Header 语言环境 */ protected Locale determineDefaultLocale(HttpServletRequest request) { Locale defaultLocale = getDefaultLocale(); if (defaultLocale == null) { defaultLocale = request.getLocale(); } return defaultLocale; } /** * 确定给定请求的默认时区,如果未找到TimeZone会话属性,则调用 * 默认实现返回指定的默认时区(如果有),否则返回null */ @Nullable protected TimeZone determineDefaultTimeZone(HttpServletRequest request) { return getDefaultTimeZone(); } } ``` # 666. 彩蛋 参考和推荐如下文章: - 韩路彪 [《看透 Spring MVC:源代码分析与实践》](https://item.jd.com/11807414.html) 的 [「第18章 LocaleResolver」](https://svip.iocoder.cn/Spring-MVC/LocaleResolver/#) 小节 - rj042 [《Spring MVC 之 LocaleResolver(解析用户区域)》](http://blog.csdn.net/rj042/article/details/23354225)