spring基本使用(23)-springMVC7-SpringMVC九大组件之本地化解析器LocaleResolver原理剖析

1、什么是本地解析器?

      Java中有一个表示本地化的一个类java.util.Locale 这个类就是用来表示当前程序的执行所在地,这个类主要描述的是本地的语言、国际信息,比如中国对应的Locale实例就是 语言lang=zh  , country=CN 这样一个Locale实例就表示中国本地。

      在程序中如何动态感知到当前程序的执行所在地信息是啥呢?这个时候就需要我们使用一个参数来进行动态获取,这就是SpringMVC中LocaleResolver的主要作用,也就是提供了国际化的一个支撑。

 

2、SpringMVC中LocaleResolver接口定义

public interface LocaleResolver {

	/**
	 * Resolve the current locale via the given request.
	 * Can return a default locale as fallback in any case.
	 * @param request the request to resolve the locale for
	 * @return the current locale (never {@code null})
	 */
        根据当前请求解析当前请求的本地化信息
	Locale resolveLocale(HttpServletRequest request);

	/**
	 * Set the current locale to the given one.
	 * @param request the request to be used for locale modification
	 * @param response the response to be used for locale modification
	 * @param locale the new locale, or {@code null} to clear the locale
	 * @throws UnsupportedOperationException if the LocaleResolver
	 * implementation does not support dynamic changing of the locale
	 */
        设置当前请求、响应的本地化信息,
	void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);

}

 

3、LocaleResolver接类图

      

      主要的实现有4个:

        3.1、AcceptHeaderLocalResovler:通过请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来决定使用具体的Locale实例,其实AcceptHeaderLocalResovler就是使用HttpServletRequest实例中的Locale实例,在进入DispatcherServlet的时候HttpServletRequest实例里面就已经有Locale实例了,可以通过request.getLocale();来获取Locale实例,HttpServletRequest里面的Locale实例就是使用请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来构建的,比如请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8 就表示zh的权重是1,en;q=0.9表示en的权重是0.9,zh-HK;q=0.8就表示zh-HK的权重是0.8,那么我么通过request.getLocale();获取到的就是权重最高的zh,然后就是构建一个zh的Locale实例,那么AcceptHeaderLocalResovler在解析Locale的时候就会返回zh的Locale实例。

        3.2、CookieLocaleResovler:根据用户在Cookie中设置的某参数来进行确定具体的本地化Locale实例。

        3.3、SessionLocaleResovler:根据用户在HttpSession中设置某参数来进行确定具体的本地化Locale实例。

        3.4、FixedLocalResovler:使用jdk自带的默认的Locale实例。

 

4、常用的LocaleResovler的配置方式

      4.1、在我们没有配置LocaleResovler的时候SpringMVC会默认为我们配置一个AcceptHeaderLocalResovler实例,还记得之前讲解的九大逐渐初始化的时候说过在DispatcherServlet.properties中SpringMVC为我们提供默认配置的时候。

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

       4.2、显示的配置CookieLocaleResovler,当我们配置了自己的LocaleResovler,那么就不会采用默认的,而是使用我们显示配置的LocaleResovler:使用场景就是比如登录完成后将本地化的参数("zh")设置到Cookie中,后续的请求就能携带本地化参数了

    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
        <!--配置指定本地化参数在cookie中的cookieName 名称-->
        <property name="cookieName" value="locale"/>
        
        <!--当前设置的name=locale 在浏览器端的最大存活时间-->
        <property name="cookieMaxAge" value="100000"/>
        
        <!--当前cookie 在那些请求路径下可见-->
        <property name="cookiePath" value="/"/>
    </bean>

       4.3、显示的配置SessionLocaleResovler,注意HttpSession中存放的不是"zh" 等字符,而是一个Locale实例。

    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
        <!--配置在HttpSession中 本地化参数的key,在SessionLocaleResolver解析的是会使用这个key去HttpSession中获取本地化Locale实例-->
        <property name="localeAttributeName" value="locale"/>
    </bean>

      4.4、显示的配置FixedLocalResovler

    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.FixedLocaleResolver"/>

       4.5、注意以上配置的spring bean id 一定要等于"localeResovler"。

 

5、如何使用LocaleResovler

      5.1、通用代码: 

    @RequestMapping("messageSource")
    private ModelAndView messageSource(Locale locale){
        入参是一个Locale实例,我们使用不同的LocaleResovler,然后来看看这个Locale会有什么变化,
        我们在使用一个消息源messageSource通过locale去不同的本地消息文件中获取key=name的值。
        String name = messageSource.getMessage("name", null, locale);
        ModelAndView modelAndView = new ModelAndView();
        //注册成功返回到注册成功的页面(success.jsp进行翻译来的页面)
        modelAndView.setViewName("success");
        modelAndView.addObject("name", name);
        return modelAndView;
    }

           

           在messages_zh_CN.properties本地消息文件中配置:name=\u6e29\u5b97\u6e90 注意由于是中文,需要转为ASCAll码

           在messages_en_US.properties本地消息文件中配置:name=wenzongyuan

           配置一个消息源messageSource:对于这个消息资源绑定不明白的可以看我之前写的博客

           <bean id="messageSource" 
            class="org.springframework.context.support.ResourceBundleMessageSource">
              <property name="basenames">
                 <list>
                    <value>i18/messages</value>
                 </list>
              </property>
           </bean>

            写一个简单的jsp用来展示结果:

         <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
         <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
         <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
         <html>
          <head>
            <title>新增用户</title>
          </head>
          <body>
            <h1>${name} 注册成功!</h1>
          </body>
         </html>

       5.2、测试AcceptHeaderLocalResovler

               

              

              返回值是中文中配置的name属性

 

      5.3、测试CookieLocaleResovler

               添加配置CookieLocaleResovler

             <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
               <!--配置指定本地化参数在cookie中的cookieName 名称-->
               <property name="cookieName" value="locale"/>

               <!--当前设置的name=locale 在浏览器端的最大存活时间-->
               <property name="cookieMaxAge" value="100000"/>

               <!--当前cookie 在那些请求路径下可见-->
               <property name="cookiePath" value="/"/>
             </bean>

                为力方便测试我们使用postman,可以手动添加cookie:

                      

                      

                        

                         

                         

                         结果显示拿到了美国的本地化消息文件中的name属性。

 

       5.4、测试SessionLocaleResovler

               1、添加SessionLocaleResovler配置:

          <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
              <!--配置在HttpSession中 本地化参数的key,在SessionLocaleResolver解析的是会使用这个key去HttpSession中获取本地化Locale实例-->
              <property name="localeAttributeName" value="locale"/>
          </bean>

                2、写一个方法用于向HttpSession中添加Locale实例:

          @RequestMapping("setSessionLocale")
          private String setSessionLocale(String mark,HttpSession httpSession){
             //我们定义一个mark标记,如果等于1 我们就往HttpSession设置 Locale.CHINA实例,否者设置为 Locale.US实例
             if (StringUtils.isEmpty(mark) || "1".equals(mark)){
                 httpSession.setAttribute("locale", Locale.CHINA);
             }else {
                 httpSession.setAttribute("locale", Locale.US);
             }
             return "register";
          }

               3、 开始测试,我们访问http://localhost:7070/test/setSessionLocale?mark=1 测试Locale.CHINA

                 

                 接着访问http://localhost:7070/test/messageSource

                 

                 

         

               4、测试,我们访问http://localhost:7070/test/setSessionLocale?mark=2 测试Locale.US

                  

                 接着访问http://localhost:7070/test/messageSource

                 

 

        5.5、FixedLocaleResolver我们不在测试,读者自行测试。

 

6、LocaleChangeInterceptor -->本地化信息修改拦截器

     6.1、有的时候我们可能会根据请求中的某参数来设置当前的本地化信息,这个时候我们上面说的几种LocaleResovler都是无法满足我们的需求的,这个时候LocaleChangeInterceptor就登场了。

     6.2、使用LocaleChangeInterceptor:

              ①、配置LocaleChangeInterceptor拦截器:

           <mvc:interceptors>
              <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="locale"/>
           </mvc:interceptors>

                      需要配置LocaleChangeInterceptor实例的paramName属性,表示使用请求中的那个参数来进行本地化信息确定,

                      不配置默认是参数名称是locale。

               ②、进行测试浏览器中输入http://localhost:7070/test/messageSource?locale=zh_CN

                       我们发现会报错,因为我们此时配置的LocaleReslover是AcceptHeaderLocaleResolver类型,AcceptHeaderLocaleResolver是不允许设置本地化信息的,源码体现如下:

	     @Override
	     public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
		     throw new UnsupportedOperationException(
				"Cannot change HTTP accept header - use a different locale resolution strategy");
	     }

                          既然AcceptHeaderLocaleResolver不然设置,那么我们就换一个LocaleReslover,我们换成CookieLocaleResolver试试,在此输入http://localhost:7070/test/messageSource?locale=en_US,结果如下:

                          

                         我们再次输入http://localhost:7070/test/messageSource?locale=zh_CN

                         

                         从此我们可以看出来LocaleChangeInterceptor跟CookieLocaleResolver可以配套使用。

                         LocaleChangeInterceptor跟SessionLocaleResovler也是可以配套使用的。

                         LocaleChangeInterceptor跟FixedLocaleResolver也是不能配套使用的。

 

7、LocaleResolver接口在整个请求中的工作原理分析

      7.1、在分析RequestMappingHandlerAdapter的原理的时候,我们着重分析了参数解析器HandlerMethodArgumentResolver我们提到过支撑Locale类型入参的参数解析器就是ServletRequestMethodArgumentResolver参数解析器,我们来查看他的支撑入参类型的方法是实现:

	     @Override
	     public boolean supportsParameter(MethodParameter parameter) {
		    Class<?> paramType = parameter.getParameterType();
		    return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				"java.time.ZoneId".equals(paramType.getName()));
	      }

   接下来我们查看ServletRequestMethodArgumentResolver的入参解析方法:我们只看如果入参类型是Locale的实现片段:

	    else if (Locale.class == paramType) {
		  return RequestContextUtils.getLocale(request);
             }

   重点方法是RequestContextUtils.getLocale(request)其实现如下:

	     public static Locale getLocale(HttpServletRequest request) {

                    使用请求实例获取一个本地信息解析器localeResolver
		    LocaleResolver localeResolver = getLocaleResolver(request);

                    然后使用解析器根据请求实例解析一个本地信息Locale实例返回,
                    如果没有解析到那就使用请求里面的Locale实例返回。
		    return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
	     }



            获取本地信息解析器的实现:
            public static LocaleResolver getLocaleResolver(HttpServletRequest request) {

                   实现方式就是从请求实例里面根据key获取到,那么问题来了,什么时候设置的呢???
		   return (LocaleResolver) 
               request.getAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE);
	    }

                           源码里面抛出个问题就是:我们配置的localeResolver是什么时候设置到请求实例里面的呢????

                           纵观源码我们找到了在DispatcherServlet中确实设置了,源码体现如下:

                            DispatcherServlet的doService方法中:

		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

                这里就是将我们配置的localeResolver设置到请求实例中。
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
			doDispatch(request, response);
		}

 

8、LocaleChangeInterceptor原理分析

      8.1、LocaleChangeInterceptor是一个标注的SpringMVC的拦截器,其实现了HandlerInterceptorAdapter抽象类,复写了拦截器的前置方法preHandle,这个方法里面使用配置的请求入参进行了本地信息实例的构建,然后进行设置。

      8.2、LocaleChangeInterceptor拦截器的前置方法实现:

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws ServletException {

                1、获取请求中配置的本地化信息确认的入参。
		String newLocale = request.getParameter(getParamName());
		if (newLocale != null) {
                        2、判断当前的请求方式是否是当前拦截器支持的请求方式,这个是可配置的。
			if (checkHttpMethod(request.getMethod())) {
                                3、看见没还是一样的,使用RequestContextUtils获取到本地信息解析器。
				LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
				if (localeResolver == null) {
					throw new IllegalStateException(
							"No LocaleResolver found: not in a DispatcherServlet request?");
				}
				try {

                                        4、使用入参去构建一个Locale实例,然后设置到本地信息解
                                           析器中,设置以后,当再次使用localeResolver去解析
                                           本地信息实例的时候,就能拿到当前的设置的本地信息实
                                           例了。
					localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
				}
				catch (IllegalArgumentException ex) {
					if (isIgnoreInvalidLocale()) {
						logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage());
					}
					else {
						throw ex;
					}
				}
			}
		}
		// Proceed in any case.
		return true;
	}

  以上就是SpringMVC的本地化信息处理的工作原理,在我们的应用需要支持国际化的时候,这个就是我们主要使用的一个方式。

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值