Spring MVC 源碼分析 - LocaleResolver 組件


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文檔是本人在學習 Spring MVC 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀

Spring 版本:5.1.14.RELEASE

該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》

LocaleResolver 組件

LocaleResolver 組件,本地化(國際化)解析器,提供國際化支持

回顧

先來回顧一下在 DispatcherServlet 中處理請求的過程中哪里使用到 LocaleResolver 組件,可以回到《一個請求的旅行過程》中的 DispatcherServletprocessDispatchResult 方法中看看,如下:

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 接口體系的結構如下:

初始化過程

DispatcherServletinitLocaleResolver(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() + "]");
        }
    }
}
  1. 獲得 Bean 名稱為 "localeResolver",類型為 LocaleResolver 的 Bean ,將其設置為 localeResolver

  2. 如果未獲得到,則獲得默認配置的 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);
}
  1. 獲取默認的語言環境

  2. 如果請求頭 Accept-Language 為空,且默認語言環境不為空,則返回默認對象 defaultLocale

  3. 從請求中獲取 Locale 對象 requestLocale

  4. 調用 getSupportedLocales 方法,獲取當前支持的 supportedLocales 集合,默認為空

  5. 如果支持的 supportedLocales 集合為空,或者包含請求中的 requestLocale ,則返回請求中的語言環境

  6. 調用 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;
    }
    
  7. 如果匹配到了則直接返回匹配結果

  8. 默認的 defaultLocale 不為空則直接返回,否則返回請求中獲取到的 requestLocale 對象

默認情況下,supportedLocalesdefaultLocale 屬性都是空的,所以 AcceptHeaderLocaleResolver 使用Accept-Language 請求頭來構造 Locale 對象

例如請求的請求頭中會有zh-CN,zh;q=0.9數據,那么這里解析出來 Locale 對象就對應language="zh" region="CN"數據

總結

本文分析了 LocaleResolver 組件,本地化(國際化)解析器,提供國際化支持。筆者實際上沒有接觸過該組件,因為目前的項目大多數都已經前后端分離了,這里只是淺顯的介紹了該接口,感興趣的可以去 Google 一下😅 有點水~

參考文章:芋道源碼《精盡 Spring MVC 源碼分析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM