跳至主要內容

18.LocaleResolver 组件

Java突击队大约 5 分钟

18.LocaleResolver 组件

Spring 版本:5.1.14.RELEASE

该系列其他文档请查看:《死磕 Spring MVC 源码分析 - 文章导读》open in new window

LocaleResolver 组件

LocaleResolver 组件,本地化(国际化)解析器,提供国际化支持

回顾

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 LocaleResolver 组件,可以回到《一个请求的旅行过程》open in new window中的 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(ApplicationContextcontext,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(HttpServletRequestrequest,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 源码分析》open in new window

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址:open in new window