概述
上一篇就Spring MVC默认的LocaleResovler(AcceptHeaderLocaleResolver)进行了分析,详细请参考https://segmentfault.com/a/1190000014797899,本节我们继续分析学习,主要分析CookieLocaleResolver
解析器(CookieLocaleResolver)
CookieLocaleResolver类继承关系如下图:
由上面类图可知,CookieLocaleResolver继承CookieGenerator并实现LocaleContextResolver接口,CookieGenerator主要是操作Cookie的工具类,LocaleContextResolver继承LacleResovler接口,增加了TimeZone信息操作。即通过Cookie实现Locale的选择。
CookieLocaleResolver类的入口是resolveLocaleContext(final HttpServletRequest request),即Spring MVC接收到客户端请求后,会调用此方法,源码如下:
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
// 解析Cookie信息
parseLocaleCookieIfNecessary(request);
// 返回Locale和TimeZone
return new TimeZoneAwareLocaleContext() {
@Override
@Nullable
public Locale getLocale() {
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
@Override
@Nullable
public TimeZone getTimeZone() {
return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
}
};
}
private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
// 第一次请求为null
if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
Locale locale = null; // 地区
TimeZone timeZone = null; // 时区
// 获取cookie的名称,取自Spring MVC配置,默认为:CookieLocaleResolver.DEFAULT_COOKIE_NAME
String cookieName = getCookieName();
if (cookieName != null) {
// 根据名称获取当前请求中的Cookie(第一次访问为null)
Cookie cookie = WebUtils.getCookie(request, cookieName);
if (cookie != null) {
// 以下主要是从客户端Cookie中解析出Locale
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);
}
try {
locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
}
catch (IllegalArgumentException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
// Error dispatch: ignore locale/timezone parse exceptions
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid locale cookie '" + cookieName +
"' with value [" + value + "] due to error dispatch: " + ex.getMessage());
}
}
else {
throw new IllegalStateException("Invalid locale cookie '" + cookieName +
"' with value [" + value + "]: " + ex.getMessage());
}
}
if (logger.isDebugEnabled()) {
logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale +
"'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : ""));
}
}
}
// 把Locale设置到请求的Attribute区,客户端请求没有携带Cookie,取Spring MVC中配置的defaultLocale
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale : determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
}
// 设置Locale
@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
// 主要是把Locale信息写回客户端
@Override
public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
Assert.notNull(response, "HttpServletResponse is required for CookieLocaleResolver");
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)));
}
实战
- 项目结构
参考上一章https://segmentfault.com/a/1190000014797899中的项目结构,本章与其一致。
- 配置文件
在Spring MVC配置文件中配置资源加载以及CookieLocaleResolver Bean,配置如下:
<!-- 国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 如果资源文件放在classpath下,basename的value必须有classpath:前缀,否则报错:No message found under code... -->
<property name="basename" value="classpath:i18n/messages" />
<!-- 如果在国际化资源文件中找不到对应代码的信息,就用这个代码作为名称返回 -->
<property name="useCodeAsDefaultMessage" value="true" />
<!--<property name="defaultEncoding" value="ISO-8859-1"/>-->
</bean>
<!-- CookieLocaleResolver解析器 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<!-- 设置cookie的名称,默认cookieName为:CookieLocaleResolver.DEFAULT_COOKIE_NAME,即(org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE) -->
<property name="cookieName" value="clientlanguage"/>
<property name="defaultLocale" value="en"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
<property name="cookieMaxAge" value="100000"/>
</bean>
<!-- 实际项目可通过Spring提供的LocaleChangeInterceptor拦截器实现,也可自定义拦截器 -->
<!--<mvc:interceptors>
<!– 该拦截器通过名为”locale”的请求参数来拦截HTTP请求,使其重新设置页面的区域化信息 –>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>-->
- 属性文件
参考上一章https://segmentfault.com/a/1190000014797899中的项目结构,本章与其一致。
- 控制器
编写Controller控制器,以便测试,代码如下:
/**
* 通过Controller修改系统Locale信息
*/
@GetMapping(value = "/setCookieLocale" , produces = "text/html;charset=UTF-8")
@ResponseBody
public String cookieLocaleResolver(HttpServletRequest request , HttpServletResponse response) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
String locale = (String)request.getParameter("locale");
localeResolver.setLocale(request , response , parseLocaleValue(locale));
return "设置Locale成功";
}
/**
* 查看Locale信息
*/
@GetMapping(value = "/getCookieLocale" , produces = "text/html;charset=UTF-8")
@ResponseBody
public String cookieLocaleResolver2(HttpServletRequest request , HttpServletResponse response) {
String clientLocale = "";
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(Cookie cookie : cookies){
clientLocale +=cookie.getName()+"="+cookie.getValue()+",";
}
}
System.out.println(clientLocale);
RequestContext requestContext = new RequestContext(request);
String value = requestContext.getMessage("message.locale");
return "客户端发送的Cookie有:"+clientLocale+" </br>当前使用的Locale是:" + requestContext.getLocale() + " </br>使用的资源Locale文件是:" + value ;
}
- 测试
以chrome为客户端,首先清除浏览器cookie,可在设置--内容管理--Cookie--查看所有Cookie和网站数据中查看是,如下图:
我们的浏览器客户端中的Cookie为空,我们还没有到调用设置Locale的方法,此时我们访问http://localhost:8089/getCookieLocale查看系统的Locale信息,如下图:
由此可见,系统使用的在Spring MVC中配置的Locale信息(详见上面的配置),并且请求头和返回头中都没有Cookie数据(服务器没有回写Cookie给客户端),接着我们调用http://localhost:8089/setCookieLocale?locale=en_US,此时,查看该请求的响应头,如下图:
由此可见,服务器端已经把Locale设置成功,并且通过response回写给客户端,当然也可查询下浏览器中已有的Cookie,如下图:
再次调用http://localhost:8089/getCookieLocale查看系统的Locale信息,如下图:
当然可以把语言环境设置为了中文,如调用http://localhost:8089/setCookieLocale?locale=zh_CN即可。
至此,我们简单分析了CookieLocaleResolver的核心源码、实现以及验证,此例仅仅是作为学习LocaleResolver,真实项目中可通过拦截器来实现,Spring提供了LocaleChangeInterceptor,也可自定义实现,其实就是重写LocaleResolver接口setLocale方法。
总结
- CookieLocaleResolver可用于没有用户会话的无状态应用程序
- 可与拦截器结合使用实现应用的国际化
最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!