1. 程式人生 > >精盡Spring MVC原始碼分析 - LocaleResolver 元件

精盡Spring MVC原始碼分析 - LocaleResolver 元件

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.2.4.RELEASE > > 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html) ## LocaleResolver 元件 `LocaleResolver` 元件,本地化(國際化)解析器,提供國際化支援 ### 回顧 先來回顧一下在 `DispatcherServlet` 中處理請求的過程中哪裡使用到 `LocaleResolver` 元件,可以回到[**《一個請求的旅行過程》**](https://www.cnblogs.com/lifullmoon/p/14131862.html)中的 `DispatcherServlet` 的 `processDispatchResult` 方法中看看,如下: ```java 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`,本地化(國際化)解析器,提供國際化支援,程式碼如下: ```java 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 元件,方法如下: ```java 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() + "]"); } } } ``` 1. 獲得 Bean 名稱為 "localeResolver",型別為 LocaleResolver 的 Bean ,將其設定為 `localeResolver` 2. 如果未獲得到,則獲得預設配置的 LocaleResolver 實現類,呼叫 `getDefaultStrategies(ApplicationContext context, Class strategyInterface)` 方法,就是從 `DispatcherServlet.properties` 檔案中讀取 LocaleResolver 的預設實現類,如下: ```properties org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver ``` 我看了一下,Spring Boot 沒有提供其他的實現類,預設也是這個 ### AcceptHeaderLocaleResolver `org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver`,實現 LocaleResolver 介面,通過檢驗 HTTP 請求的`Accept-Language`頭部來解析區域,預設的實現類 #### 構造方法 ```java public class AcceptHeaderLocaleResolver implements LocaleResolver { private final List supportedLocales = new ArrayList<>(4); @Nullable private Locale defaultLocale; } ``` 上面兩個屬性預設都沒有設定值 #### resolveLocale 實現 `resolveLocale(HttpServletRequest request)` 方法,從請求中解析出 `java.util.Locale` 物件,方法如下: ```java @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 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); } ``` 1. 獲取預設的語言環境 2. 如果請求頭 `Accept-Language` 為空,且預設語言環境不為空,則返回預設物件 `defaultLocale` 3. 從請求中獲取 Locale 物件 `requestLocale` 4. 呼叫 `getSupportedLocales` 方法,獲取當前支援的 `supportedLocales` 集合,預設為空 5. 如果支援的 `supportedLocales` 集合為空,或者包含請求中的 `requestLocale` ,則返回請求中的語言環境 6. 呼叫 `findSupportedLocale(HttpServletRequest request, List supportedLocales)` 方法,從請求中的 Locale 們和支援的 Locale 集合進行匹配,如下: ```java @Nullable private Locale findSupportedLocale(HttpServletRequest request, List supportedLocales) { Enumeration 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; } ``` 7. 如果匹配到了則直接返回匹配結果 8. 預設的 `defaultLocale` 不為空則直接返回,否則返回請求中獲取到的 `requestLocale` 物件 預設情況下,`supportedLocales` 與`defaultLocale` 屬性都是空的,所以 AcceptHeaderLocaleResolver 使用`Accept-Language` 請求頭來構造 Locale 物件 例如請求的請求頭中會有`zh-CN,zh;q=0.9`資料,那麼這裡解析出來 Locale 物件就對應`language="zh" region="CN"`資料 ### 總結 本文分析了 `LocaleResolver` 元件,本地化(國際化)解析器,提供國際化支援。筆者實際上沒有接觸過該元件,因為目前的專案大多數都已經前後端分離了,這裡只是淺顯的介紹了該介面,感興趣的可以去 Google 一下:sweat_smile: 有點水~ > 參考文章:**芋道原始碼**[《精盡 Spring MVC 原始碼分析》](http://svip.iocoder.cn/categories/Spri