該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他文件請檢視:《精盡 Spring MVC 原始碼分析 - 文章導讀》
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 一下? 有點水~
參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》