LocaleResolver 组件
LocaleResolver
组件,本地化(国际化)解析器,提供国际化支持
回顾
先来回顾一下在 DispatcherServlet
中处理请求的过程中哪里使用到 LocaleResolver
组件,可以回到 《一个请求的旅行过程》 中的 DispatcherServlet
的 processDispatchResult
方法中看看,如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// ... 省略相关代码
// <3> 是否进行页面渲染
if (mv != null && !mv.wasCleared()) {
// <3.1> 渲染页面
render(mv, request, response);
// <3.2> 清理请求中的错误消息属性
// 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
// ... 省略相关代码
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
// <1> 解析 request 中获得 Locale 对象,并设置到 response 中
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
// ... 省略相关代码
// 获得 View 对象
View view;
String viewName = mv.getViewName();
// ... 省略相关代码
view = mv.getView();
// ... 省略相关代码
try {
// <3> 设置响应的状态码
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// <4> 渲染页面
view.render(mv.getModelInternal(), request, response);
}
// ... 省略相关代码
}
在执行完handler
处理器后,需要对返回的 ModelAndView 对象进行处理,可能需要调用 render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染页面
可以看到需要先通过 LocaleResolver 从请求中解析出 java.util.Locale
对象
LocaleResolver 接口
org.springframework.web.servlet.LocaleResolver
,本地化(国际化)解析器,提供国际化支持,代码如下:
public interface LocaleResolver {
/**
* 从请求中,解析出要使用的语言。例如,请求头的 "Accept-Language"
*/
Locale resolveLocale(HttpServletRequest request);
/**
* 设置请求所使用的语言
*/
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
LocaleResolver 接口体系的结构如下:
初始化过程
在 DispatcherServlet
的 initLocaleResolver(ApplicationContext context)
方法,初始化 LocaleResolver 组件,方法如下:
private void initLocaleResolver(ApplicationContext context) {
try {
// 从上下文中获取Bean名称为'localeResolver'的对象
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
/**
* 从配置文件中获取默认的 {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}
*/
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
- 获得 Bean 名称为 "localeResolver",类型为 LocaleResolver 的 Bean ,将其设置为
localeResolver
- 如果未获得到,则获得默认配置的 LocaleResolver 实现类,调用
getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法,就是从DispatcherServlet.properties
文件中读取 LocaleResolver 的默认实现类,如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
我看了一下,Spring Boot 没有提供其他的实现类,默认也是这个
AcceptHeaderLocaleResolver
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
,实现 LocaleResolver 接口,通过检验 HTTP 请求的Accept-Language
头部来解析区域,默认的实现类
构造方法
public class AcceptHeaderLocaleResolver implements LocaleResolver {
private final List<Locale> supportedLocales = new ArrayList<>(4);
@Nullable
private Locale defaultLocale;
}
上面两个属性默认都没有设置值
resolveLocale
实现 resolveLocale(HttpServletRequest request)
方法,从请求中解析出 java.util.Locale
对象,方法如下:
@Override
public Locale resolveLocale(HttpServletRequest request) {
// <1> 获取默认的语言环境
Locale defaultLocale = getDefaultLocale();
// <2> 如果请求头 'Accept-Language' 为空,且默认语言环境不为空,则返回默认的
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
// <3> 从请求中获取 Locale 对象 `requestLocale`
Locale requestLocale = request.getLocale();
// <4> 获取当前支持的 `supportedLocales` 集合
List<Locale> supportedLocales = getSupportedLocales();
// <5> 如果支持的 `supportedLocales` 集合为空,或者包含请求中的 `requestLocale` ,则返回请求中的语言环境
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
// <6> 从请求中的 Locale 们和支持的 Locale 集合进行匹配
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
// <7> 如果匹配到了则直接返回匹配结果
if (supportedLocale != null) {
return supportedLocale;
}
// <8> 默认的 `defaultLocale` 不为空则直接返回,否则返回请求中获取到的 `requestLocale` 对象
return (defaultLocale != null ? defaultLocale : requestLocale);
}
- 获取默认的语言环境
- 如果请求头
Accept-Language
为空,且默认语言环境不为空,则返回默认对象defaultLocale
- 从请求中获取 Locale 对象
requestLocale
- 调用
getSupportedLocales
方法,获取当前支持的supportedLocales
集合,默认为空 - 如果支持的
supportedLocales
集合为空,或者包含请求中的requestLocale
,则返回请求中的语言环境 - 调用
findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales)
方法,从请求中的 Locale 们和支持的 Locale 集合进行匹配,如下:
@Nullable
private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
Enumeration<Locale> requestLocales = request.getLocales();
Locale languageMatch = null;
while (requestLocales.hasMoreElements()) {
Locale locale = requestLocales.nextElement();
if (supportedLocales.contains(locale)) {
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
// Full match: language + country, possibly narrowed from earlier language-only match
return locale;
}
}
else if (languageMatch == null) {
// Let's try to find a language-only match as a fallback
for (Locale candidate : supportedLocales) {
if (!StringUtils.hasLength(candidate.getCountry()) &&
candidate.getLanguage().equals(locale.getLanguage())) {
languageMatch = candidate;
break;
}
}
}
}
return languageMatch;
}
- 如果匹配到了则直接返回匹配结果
- 默认的
defaultLocale
不为空则直接返回,否则返回请求中获取到的requestLocale
对象
默认情况下,supportedLocales
与defaultLocale
属性都是空的,所以 AcceptHeaderLocaleResolver 使用Accept-Language
请求头来构造 Locale 对象
例如请求的请求头中会有zh-CN,zh;q=0.9
数据,那么这里解析出来 Locale 对象就对应language="zh" region="CN"
数据
总结
本文分析了 LocaleResolver
组件,本地化(国际化)解析器,提供国际化支持。笔者实际上没有接触过该组件,因为目前的项目大多数都已经前后端分离了,这里只是浅显的介绍了该接口,感兴趣的可以去 Google 一下 有点水~
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。