该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.2.4.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 – 文章导读》

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

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 LocaleResolver 组件,可以回到《一个请求的旅行过程》中的 DispatcherServletprocessDispatchResult 方法中看看,如下:

  1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  2. @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  3. @Nullable Exception exception) throws Exception {
  4. // ... 省略相关代码
  5. // <3> 是否进行页面渲染
  6. if (mv != null && !mv.wasCleared()) {
  7. // <3.1> 渲染页面
  8. render(mv, request, response);
  9. // <3.2> 清理请求中的错误消息属性
  10. // 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下
  11. if (errorView) {
  12. WebUtils.clearErrorRequestAttributes(request);
  13. }
  14. }
  15. // ... 省略相关代码
  16. }
  17. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
  18. // Determine locale for request and apply it to the response.
  19. // <1> 解析 request 中获得 Locale 对象,并设置到 response 中
  20. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
  21. response.setLocale(locale);
  22. // ... 省略相关代码
  23. // 获得 View 对象
  24. View view;
  25. String viewName = mv.getViewName();
  26. // ... 省略相关代码
  27. view = mv.getView();
  28. // ... 省略相关代码
  29. try {
  30. // <3> 设置响应的状态码
  31. if (mv.getStatus() != null) {
  32. response.setStatus(mv.getStatus().value());
  33. }
  34. // <4> 渲染页面
  35. view.render(mv.getModelInternal(), request, response);
  36. }
  37. // ... 省略相关代码
  38. }

在执行完handler处理器后,需要对返回的 ModelAndView 对象进行处理,可能需要调用 render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) 方法,渲染页面

可以看到需要先通过 LocaleResolver 从请求中解析出 java.util.Locale 对象

org.springframework.web.servlet.LocaleResolver,本地化(国际化)解析器,提供国际化支持,代码如下:

  1. public interface LocaleResolver {
  2. /**
  3. * 从请求中,解析出要使用的语言。例如,请求头的 "Accept-Language"
  4. */
  5. Locale resolveLocale(HttpServletRequest request);
  6. /**
  7. * 设置请求所使用的语言
  8. */
  9. void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
  10. }

LocaleResolver 接口体系的结构如下:

DispatcherServletinitLocaleResolver(ApplicationContext context) 方法,初始化 LocaleResolver 组件,方法如下:

  1. private void initLocaleResolver(ApplicationContext context) {
  2. try {
  3. // 从上下文中获取Bean名称为'localeResolver'的对象
  4. this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
  5. if (logger.isTraceEnabled()) {
  6. logger.trace("Detected " + this.localeResolver);
  7. }
  8. else if (logger.isDebugEnabled()) {
  9. logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
  10. }
  11. }
  12. catch (NoSuchBeanDefinitionException ex) {
  13. // We need to use the default.
  14. /**
  15. * 从配置文件中获取默认的 {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}
  16. */
  17. this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
  18. if (logger.isTraceEnabled()) {
  19. logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
  20. "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
  21. }
  22. }
  23. }
  1. 获得 Bean 名称为 “localeResolver”,类型为 LocaleResolver 的 Bean ,将其设置为 localeResolver

  2. 如果未获得到,则获得默认配置的 LocaleResolver 实现类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 LocaleResolver 的默认实现类,如下:

    1. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

我看了一下,Spring Boot 没有提供其他的实现类,默认也是这个

org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver,实现 LocaleResolver 接口,通过检验 HTTP 请求的Accept-Language头部来解析区域,默认的实现类

  1. public class AcceptHeaderLocaleResolver implements LocaleResolver {
  2. private final List<Locale> supportedLocales = new ArrayList<>(4);
  3. @Nullable
  4. private Locale defaultLocale;
  5. }

上面两个属性默认都没有设置值

实现 resolveLocale(HttpServletRequest request) 方法,从请求中解析出 java.util.Locale 对象,方法如下:

  1. @Override
  2. public Locale resolveLocale(HttpServletRequest request) {
  3. // <1> 获取默认的语言环境
  4. Locale defaultLocale = getDefaultLocale();
  5. // <2> 如果请求头 'Accept-Language' 为空,且默认语言环境不为空,则返回默认的
  6. if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
  7. return defaultLocale;
  8. }
  9. // <3> 从请求中获取 Locale 对象 `requestLocale`
  10. Locale requestLocale = request.getLocale();
  11. // <4> 获取当前支持的 `supportedLocales` 集合
  12. List<Locale> supportedLocales = getSupportedLocales();
  13. // <5> 如果支持的 `supportedLocales` 集合为空,或者包含请求中的 `requestLocale` ,则返回请求中的语言环境
  14. if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
  15. return requestLocale;
  16. }
  17. // <6> 从请求中的 Locale 们和支持的 Locale 集合进行匹配
  18. Locale supportedLocale = findSupportedLocale(request, supportedLocales);
  19. // <7> 如果匹配到了则直接返回匹配结果
  20. if (supportedLocale != null) {
  21. return supportedLocale;
  22. }
  23. // <8> 默认的 `defaultLocale` 不为空则直接返回,否则返回请求中获取到的 `requestLocale` 对象
  24. return (defaultLocale != null ? defaultLocale : requestLocale);
  25. }
  1. 获取默认的语言环境

  2. 如果请求头 Accept-Language 为空,且默认语言环境不为空,则返回默认对象 defaultLocale

  3. 从请求中获取 Locale 对象 requestLocale

  4. 调用 getSupportedLocales 方法,获取当前支持的 supportedLocales 集合,默认为空

  5. 如果支持的 supportedLocales 集合为空,或者包含请求中的 requestLocale ,则返回请求中的语言环境

  6. 调用 findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) 方法,从请求中的 Locale 们和支持的 Locale 集合进行匹配,如下:

    1. @Nullable
    2. private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
    3. Enumeration<Locale> requestLocales = request.getLocales();
    4. Locale languageMatch = null;
    5. while (requestLocales.hasMoreElements()) {
    6. Locale locale = requestLocales.nextElement();
    7. if (supportedLocales.contains(locale)) {
    8. if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
    9. // Full match: language + country, possibly narrowed from earlier language-only match
    10. return locale;
    11. }
    12. }
    13. else if (languageMatch == null) {
    14. // Let's try to find a language-only match as a fallback
    15. for (Locale candidate : supportedLocales) {
    16. if (!StringUtils.hasLength(candidate.getCountry()) &&
    17. candidate.getLanguage().equals(locale.getLanguage())) {
    18. languageMatch = candidate;
    19. break;
    20. }
    21. }
    22. }
    23. }
    24. return languageMatch;
    25. }
  7. 如果匹配到了则直接返回匹配结果

  8. 默认的 defaultLocale 不为空则直接返回,否则返回请求中获取到的 requestLocale 对象

默认情况下,supportedLocalesdefaultLocale 属性都是空的,所以 AcceptHeaderLocaleResolver 使用Accept-Language 请求头来构造 Locale 对象

例如请求的请求头中会有zh-CN,zh;q=0.9数据,那么这里解析出来 Locale 对象就对应language="zh" region="CN"数据

本文分析了 LocaleResolver 组件,本地化(国际化)解析器,提供国际化支持。笔者实际上没有接触过该组件,因为目前的项目大多数都已经前后端分离了,这里只是浅显的介绍了该接口,感兴趣的可以去 Google 一下

版权声明:本文为lifullmoon原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lifullmoon/p/14137799.html