該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》git
LocaleResolver
組件,本地化(國際化)解析器,提供國際化支持github
先來回顧一下在 DispatcherServlet
中處理請求的過程當中哪裏使用到 LocaleResolver
組件,能夠回到《一個請求的旅行過程》中的 DispatcherServlet
的 processDispatchResult
方法中看看,以下:web
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)
方法,渲染頁面spring
能夠看到須要先經過 LocaleResolver 從請求中解析出 java.util.Locale
對象後端
org.springframework.web.servlet.LocaleResolver
,本地化(國際化)解析器,提供國際化支持,代碼以下:app
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 組件,方法以下:ide
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 沒有提供其餘的實現類,默認也是這個
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(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 源碼分析》