springmvc更多家族成员--国际化视图与localresolver---10(代码片段)

大忽悠爱忽悠 大忽悠爱忽悠     2022-11-30     445

关键词:

Spring MVC更多家族成员--国际化视图与LocalResolver---10


引言

网络拉近了人与人之间的距离。即使相距千里,人们也可以通过网络互相了解对方的信息和文化。但是,不管怎么说,在“地球村”没有统一的“官方语言”之前,不同地区的不同语言依然是人们能够互相交流的一道障碍。所以,现在的Web应用程序尤其是企业级的应用,都会提供国际化的信息支持,以便可以根据访问者的Locale信息为他们提供相应语言的信息内容。为用户提供国际化视图支持自然成为Spring MVC框架不可或缺的一部分。

在ViewResolver根据逻辑视图名解析视图的时候,ViewResolver的resolveviewName(viewName,locale)方法除了接受要解析的逻辑视图名作为参数之外,还同时接受一个Locale类型对象。这样,ViewResolver就可以根据Locale的不同而返回针对不同Locale的视图实例。

到此为止,好像没有必要再往下看了,ViewResolver的设计已经足以完成国际化视图支持的使命了,不是吗?

难道ResourceBundleviewResolver不就是很好的例证吗?

实际上,从ViewResolver这个层次上来讲,情况确实如此。

但是,我们可曾想过,ViewResolver所接受的Locale实例是从何而来的呢?如何获取用户所对应的Localel呢?

只有揭开这一谜团,才能将Spring MVC框架内对国际化视图的支持讲述完整。

可以有多种方式获取用户通过浏览器提交的Web请求所对应的Locale值,比如,根据HTTP的Accept-Language协议头进行解析,或者读取用户浏览器端存储的相应Cookie值等。鉴于有如此多不同的处理方式,Spring MVC使LocaleResolver接口定义对各种可能的Locale值的获取/解析方式进行统一的策略抽象。

该接口定义如下:

public interface LocaleResolver 

	Locale resolveLocale(HttpServletRequest request);

	void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);


作为策略接口,LocaleResolver.主要完成两个工作:

  • 第一,由resolveLocale(request)方法负责根据当前Locale解析策略获取当前请求对应的Locale值
  • 第二,如果当前策略支持Locale的更改,那么可以通过setLocale方法对当前策略默认取得的Locale值进行变更。

可用的LocaleResolver

根据通常的Locale获取策略,Spring MVC为LocaleResolver提供了相应的可用实现类,如下所述。

  • FixedLocaleResolver。最简单的LocaleResolver实现类。一旦指定给FixedLocaleResolver一个Locale值,FixedLocaleResolver将一直持有并返回这个Locale保持不变。因为该FixedLocaleResolver的策略是保持一个Locale值不变,所以不能通过setLocale更改FixedLocaleResolver默认返回的Locale值。
  • AcceptHeaderLocaleResolver。用户通过客户端浏览器提交Web请求之后,HTTP的Accept-Language协议头(HTTP Header)将随同Web请求一同发送给服务器端进行处理。AcceptHeaderLocaleResolver的策略就是根据Accept-Language协议头来解析并返回当前Web请求对应的Locale值。既然我们无法更改Accept-Language协议头,那么AcceptHeaderLocaleResolver与FixedLocaleResolver一样无法更改默认策略返回的Locale值。
  • SessionLocaleResolver。SessionLocaleResolver将根据指定键值从Session中获取相应的Locale。初始的时候,我们可以为其指定一个默认的Locale值。如果SessionLocaleResolver既无法从Session获取可用的Locale值,又没有初始化的默认Locale,那么它将采用AcceptHeaderLocaleResolver的策略获取Web请求对应的Locale值。因为SessionLocaleResolver是以Session进行Locale管理,所以我们可以对SessionLocaleResolver默认所返回的Locale值进行变更。
  • CookieLocaleResolver。如果客户端浏览器没有禁止使用Cookie的话,我们也可以使用 Cookie来管理Locale信息。CookieLocaleResolver通过读取客户端的指定Cookie获取相应的 Locale值,当然,我们在初始之初就可以为CookieLocaleResolver指定一个默认返回的Locale值。当CookieLocaleResolver无法从客户端的Cookie获取相应的Locale的时候,它可以转而 返回这个初始化时候指定的默认Locale值。如果以上尝试均告失败,那么CookieLocaleResolver也就不得不与SessionLocaleResolver那样,转而使用AcceptHeaderLocaleResolver的策略来获取Locale值了。只要客户端浏览器不禁止Cookie的使用,我们就可以对Cookie中的数据进行更新。所以CookieLocaleResolver支持通过setLocale方法更改默 认返回的Locale值。

以上实现类全部位于org.springframework.web.servlet.i18n包中。我们可以根据当前Web应用程序的需要选择使用其中任何一个,有关各个实现类的API细节,不妨参照相应类的Javadoc。


LocaleResolver的足迹

要在Spring MVC应用中使用相应的LocaleResolver对Locale进行解析和设置,只需要将相 应实现类添加到DispatherServlet的webApplicationContext中。在合适的时机,该LocaleResolver将被使用。我们以SessionLocaleResolver为例,给出对应的配置内容如下:

     <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
          <property name="defaultLocale" value="zh_CN"/>
     </bean>

在把要使用的LocaleResolver实现类添加到容器的过程中需要注意,名称“localeResolver” 是必须的。因为DispatcherServlet在初始化的时候,将按照该指定名称到webApplicationContext 中去查找可用的LocaleResolver实例。如果找不到,DispatcherServlet将使用默认的LocaleResolver。

	private void initLocaleResolver(ApplicationContext context) 
		try 
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
			if (logger.isDebugEnabled()) 
				logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
			
		
		catch (NoSuchBeanDefinitionException ex) 
			// We need to use the default.
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
			if (logger.isDebugEnabled()) 
				logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
						"': using default [" + this.localeResolver + "]");
			
		
	

如果只使用LocaleResolver,那么将相应实现类添加到webApplicationContext之后我们 就可以收手了。不过,要是想进一步了解添加到webApplicationContext的LocaleResolver实 例都可以在Web请求处理过程中的哪些时间点发挥作用,LocaleResolver走过的几个点还是需要知道一下的。


LocaleResolver在初始化流程中的使用


可以看到DispatcherServlet的继承体系,并且在请求到来的时候,最终会调用到Servlet的service方法,因此我们先从service方法开始追溯:

  • 由于FrameworkServlet覆写了父类service方法的实现,因此请求到来的时候,首先调用到的是FrameworkServlet覆写的service方法实现
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		 //当请求放为PATCH或者请求方法为空的时候,调用processRequest处理请求--该方法是DispathcerServlet处理请求的核心方法 
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) 
			processRequest(request, response);
		
		else 
		//调用父类service方法实现--这里调用到的是HttpServlet的service方法实现
			super.service(request, response);
		
	
  • HttpServlet#对service方法首次给出了实现
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) 
                ...
                doGet(req, resp);
               ... 
         else if (method.equals(METHOD_HEAD)) 
            doHead(req, resp);
         else if (method.equals(METHOD_POST)) 
            doPost(req, resp);
         else if (method.equals(METHOD_PUT)) 
            doPut(req, resp);
         else if (method.equals(METHOD_DELETE)) 
            doDelete(req, resp);
         else if (method.equals(METHOD_OPTIONS)) 
            doOptions(req,resp);
         else if (method.equals(METHOD_TRACE)) 
            doTrace(req,resp);
         else 
        //如果当前请求方法是不被支持的,那么就响应对应的错误
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        
    

HttpServlet相关doGet,doPost等方法的实现,这里最终都调用的是FrameworkServlet的实现:


而FrameworkServlet对于这些doGet,doPost方法的实现,最终都是调用的processRequest方法的实现。

processRequest方法是我们关注的核心,该方法中完成了对LocaleContext的绑定工作:


processRequest处理请求的核心方法

	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
       //尝试从LocaleContextHolder中取出当前线程先前绑定的LocaleContext---》ThreadLocal
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		//为当前请求构建一个LocaleContext---buildLocaleContext调用的是DispathcerServlet覆写的方法
		//利用LocaleContextResolver解析当前request得到一个LocaleContext实现
		LocaleContext localeContext = buildLocaleContext(request);
        
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		
        //将上面得到的localeContext绑定到LocaleContextHolder上
		initContextHolders(request, localeContext, requestAttributes);

		try 
		   //调用DispathcerServlet的doService方法
			doService(request, response);
		
		catch (ServletException | IOException ex) 
			failureCause = ex;
			throw ex;
		
		catch (Throwable ex) 
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		

		finally 
		    //将相关contextHolder还原到初始模样---为啥要取出previousLocaleContext,就是为了当前请求处理完毕后,再还原会去
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) 
				requestAttributes.requestCompleted();
			
			logResult(request, response, failureCause, asyncManager);
			publishRequestHandledEvent(request, response, startTime, failureCause);
		
	
  • buildLocaleContext
	protected LocaleContext buildLocaleContext(final HttpServletRequest request) 
		LocaleResolver lr = this.localeResolver;
		if (lr instanceof LocaleContextResolver) 
			return ((LocaleContextResolver) lr).resolveLocaleContext(request);
		
		else 
			return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
		
	

调用DispathcerServlet内部的LocaleContextResolver对当前request进行解析

  • initContextHolders
	private void initContextHolders(HttpServletRequest request,
			@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) 
		if (localeContext != null) 
			LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
		
		if (requestAttributes != null) 
			RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		
	

将上面得到的localeContext绑定到LocaleContextHolder上

  • resetContextHolders
	private void resetContextHolders(HttpServletRequest request,
			@Nullable LocaleContext prevLocaleContext, @Nullable RequestAttributes previousAttributes) 

		LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
		RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
	

还原相关ContextHolder的状态


DispatcherServlet的doservice方法

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception 
		logRequest(request);
		...		
		// Make framework objects available to handlers and view objects.
		//在这里将很多工具都放在了request对象的属性集合中,相当于暴露给了handler和view视图对象使用
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 
        ...
			doDispatch(request, response);
        ...
	

小结

在DispatcherServlet要处理接收到Web请求之前,它会将其在初始化的时候获取的LocaleResolver实例,以及通过该LocaleResolver解析后的Locale值,以LocaleContext的形式绑定到当前线程。这样,在需要的时候,后继处理流程就可以通过绑定的LocaleContext获得当前Locale值以及使用的LocaleResolver。


LocaleResolver后继使用时机

流程按照我们之前所描述的顺序执行,在ViewResolver行动之前,DispatcherServlet将使用初始化、

ViewResolver从“宏观上”解决了对应不同Locale的视图选取问题。

不过,如果我们要在具体的视图内访问Locale信息该怎么办?

原则上来说,我们是通过RequestContext访问必要的国际化信息,包括当前请求对应的 Locale、应用使用的LocaleResolver,以及MessageSource中的国际化信息。

如果使用 JSP作为视图技术,那么直接使用Spring MVC提供的自定义Tag就可以,比如<spring:messagecode=". .”/>,因为它底层就是通过RequestContext完成相应功能的。但是, 如果使用velocity/Freemarker之类的视图技术,我们就不得不直接使用RequestContext来完成这些相关信息的访问了。而通过设置AbstractView的requestContextAttribute属性可以让我们在这些视图中获取到RequestContext的支持。

最后一步是清理的工作,DispatcherServlet将恢复以LocaleContext形式绑定到当前线程 的Locale相关信息。

现在,跟着LocaleResolver的足迹走过一遍之后,各位是否已经理解了LocaleResolver的存在价值了呢?


体会

当我们需要在一个工作处理流中任意节点都可以获取某个模型对象,那么有下面两种方法:

  • 将模型对象放入ThreadLocal中,与当前线程绑定。
  • 如果在当前工作流中存在某个对象的生命周期与当前工作流一致,例如: Web请求处理流程中的Request对象,那么我们可以将模型对象与当前Request对象相绑定。

并且通常会将整个工作流中需要用的模型对象,都交给一个Context上下文对象保存,对应上下文对象的生命周期和对应的工作流一致,例如: 会将请求处理工作流中需要用的对象都放入RequestContext中。


Locale的变更与LocaleChangeHandler

当访问各种支持国际化信息页面的网站的时候,即使本地的默认Locale使得服务器返回的是英文的信息页面,我们依然可以点击页面中的相应链接更改这一结果,比如,点击“中文版”切换到中文信息页面。在基于Spring MVC的Web应用中,我们要如何实现这一功能呢?

我们已经介绍了4种LocaleResolver的策略实现,为FixedLocaleResolver,AcceptHeaderLocaleResolver,SessionLocaleResolver和CookieLocaleResolver。前两种实现显然不支持Locale的变更,所以,如果要实现根据用户选择来切换Locale这样的功能需求,我们只能选择SessionLocaleResolver或者CookieLocaleResolve。在这样的前提下,我们再寻求下一步的解决 方案。

国际化信息页面的选择是由ViewResolver所接受的Locale决定的。要让用户能够变更到其他语言内容的信息页面,我们只要根据用户提交的请求内容变更Locale值即可。

在介绍HandlerInterceptor的时候,我们提到LocaleChangeInterceptor,而这里就是它的“用武之地”了。

LocaleChangeInterceptor的工作原理十分简单,它根据某一个请求参数获取要切换到的Locale 信息(该参数默认名称为“locale”),然后通过相应LocaleResolver实现类的setLocale方法, 使用新获取的Locale信息替换掉所使用的LocaleResolver默认策略所返回的Locale值。

要根据用户请求进行面向不同Locale的视图切换,我们只要配置一个LocaleChangeInterceptor对用户请求进行拦 截即可,该拦截器的核心preHandle方法如下:

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws ServletException 
        //getParamName()返回值默认为locale
		String newLocale = request.getParameter(getParamName());
		if (newLocale != null) 
		//只有指定的HttpMethod才能被允许切换locale
			if (checkHttpMethod(request.getMethod())) 
				LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
				if (localeResolver == null) 
					throw new IllegalStateException(
							"No LocaleResolver found: not in a DispatcherServlet request?");
				
				try 
					localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
				
				catch (IllegalArgumentException ex) 
					if (isIgnoreInvalidLocale()) 
						if (logger.isDebugEnabled()) 
							logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage());
						
					
					else 
						throw ex;
					
				
			
		
		// Proceed in any case.
		return true;
	

如果要使用该拦截器,那么具体配置如下:

     <bean id="handlerMapping" class="com.example.AnnoHandlerMapping">
          <property name="interceptors">
               <list>
                    <ref bean="localeChangeInterceptor"/>
               </list>
          </property>
     </bean>

     <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>

     <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
          <property name="defaultLocale" value="zh_CN"/>
     </bean>

springmvc更多家族成员---框架内处理流程拦截与handlerinterceptor---08(代码片段)

SpringMVC更多家族成员---框架内处理流程拦截与HandlerInterceptor---08引言preHandlepostHandleafterCompletionHandlerExecutionChain源码概览可用的HandlerInterceptor实现UserRoleAuthorizationInterceptorWebContentInterceptor自定义HandlerIn 查看详情

springmvc更多家族成员----handler与handleradaptor---07(代码片段)

SpringMVC更多家族成员----Handler与HandlerAdaptor---07问题的起源深入了解Handler自定义Handler近看HandlerAdaptor的奥秘告知Handler与HandlerAdaptor的存在案例问题的起源最初为了降低理解的难度,我们说,HandlerMapping将会通过HandlerExecutionc... 查看详情

springmvc更多家族成员----文件上传---06(代码片段)

SpringMVC更多家族成员----文件上传---06本节导读文件上传与MultipartResolver使用MultipartResolver进行文件上传的简单分析StandardServletMultipartResolver概览注意StandardMultipartHttpServletRequest概览StandardMultipartFile概览CommonsMultipartReso 查看详情

springmvc更多家族成员--主题(theme)与themeresolver(代码片段)

SpringMVC更多家族成员--Theme与ThemeResolver引言提供主题资源的ThemeSource管理主题的ThemeResolver切换主题的ThemeChangeInterceptor引言不管是使用Windows操作系统还是使用Linux操作系统,当我们对某种风格的桌面主题感到厌烦的时候,... 查看详情

springboot与springmvc的区别

参考技术ASpringMVC和SpringBoot都是Spring家族的重要成员。Spring家族的使命就是为了简化而生。SpringMVC简化日常Web开发的,后来随着自身的发展,SpringMVC变得臃肿复杂,而SpringBoot则进一步简化了SpringMVC开发。SpringMVC为JavaWeb而生。Sprin... 查看详情

程序的国际化(java与springmvc)(代码片段)

程序的国际化(Java与SpringMVC)国际化是商业软件的一个基本要求,因为当今的软件系统需要面对全球的浏览者。程序国际化的目的就是根据用户的语言环境的不同向用户输入与之相对应的页面,以示友好。Java的国家化思想&... 查看详情

springmvc的特点

参考技术A1、SpringMVC具有强大的灵活性、非侵入性和可配置型。2、SpringMVC提供了一个前端控制器DispatcherServlet,尅发着无须额外开发控制器对象。3、SpringMVC分工明确,包括控制器、验证器、命令对象、模型对象、处理程序映射视... 查看详情

UICollectionView 在顶部加载项目 - 加载更多选项

】UICollectionView在顶部加载项目-加载更多选项【英文标题】:UICollectionViewloaditemsontop-loadmoreoption【发布时间】:2016-08-0817:03:06【问题描述】:我用meteor-iOS构建消息应用程序,一切都很顺利,但我正在尝试加载更多功能,但我无法... 查看详情

视图家族

视图家族"""views:视图generics:工具视图mixins:视图工具集viewsets:视图集""""""学习曲线APIView=>GenericAPIView=>mixins的五大工具类=>generics中的工具视图=>viewsets中的视图集"""  GenericAPIView基类#GenericAPIView是继承APIView的, 查看详情

javaweb017——springmvc(代码片段)

一、什么是SpringMVC?1.1、SpringMVC概述SpringMVC是Spring提供的一个实现了WebMVC设计模式的轻量级Web框架。它与Struts2框架一样,都属于MVC框架,但其使用和性能等方面比Struts2更加优异。1.2、SpringMVC具有以下特点:是Sprin... 查看详情

springmvc中的mybatis怎么使用

springmvc+myBatis配置详解一、springmvcSpring框架(框架即:编程注解+xml配置的方式)MVC是Spring框架的一大特征,Spring框架有三大特征(IOC(依赖注入),AOP(面向切面),MVC(建模M-视图V-控制器C)。框架一般用于团队开发,使用分... 查看详情

springmvc国际化

   默认情况下,SpringMvc根据Accept-Language参数判断客户端的本地化类型。当接受到请求时,SpringMvc会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。SpringMvc还允许装... 查看详情

hadoop之家族成员pig简介

...许多的子项目,今天的内容就是简单的介绍一下Hadoop家族的子项目中的Pig。下图是一个Hadoop子项目的大体结构图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-INUf7iRp-1662106189048)(http://172.18.3.4:8090/... 查看详情

jstlview快速国际化(springmvc)

JSTLView:快速国际化;只要导入了jstl的jar包,以前默认创建的InternalResouceView都会被使用jstlView替代;  国际化的新步骤:     1)、写好国际化资源文件     il118_en_US.properties    il118_zh_CN.propert... 查看详情

spring-boot-starter家族成员简介

以下应用程序starters是SpringBoot在org.springframework.boot组下提供的:名称描述spring-boot-starter核心SpringBootstarter,包括自动配置支持,日志和YAMLspring-boot-starter-actuator生产准备的特性,用于帮我们监控和管理应用spring-boot-starter-amqp对”... 查看详情

spring-boot-starter家族成员简介

下应用程序starters是SpringBoot在org.springframework.boot组下提供的:名称描述spring-boot-starter核心SpringBootstarter,包括自动配置支持,日志和YAMLspring-boot-starter-actuator生产准备的特性,用于帮我们监控和管理应用spring-boot-starter-amqp对”... 查看详情

springboot与springmvc的区别

...然后在此两者的基础上实现了其他延伸产品的高级功能。SpringMVC是基于Servlet的一个MVC框架主要解决WEB开发的问题,因为Spring的配置非 查看详情

intellijideaultimate家族新成员bigdatatools——集成zeppelin和spark

...除段落运行段落浏览段落的输出支持基本的可视化将会有更多的语言和集成。路线图即将发布,敬请关注。快速说明确保版本为IntelliJIDEAUltimate2019.2* 确保已安装BashSupport,Python和Scala插件在设置|插件,切换到“市场”选项卡,... 查看详情