Spring MVC各组件近距离接触--下--04

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC各组件近距离接触--下--04相关的知识,希望对你有一定的参考价值。

Spring MVC各组件近距离接触--下--04


引言

前面两节主要介绍了Spring mvc中的HandlerMapping和Controller,下面来介绍一下mvc中的其他常见组件。


ModelAndView

Controller在将Web请求处理完成后,会返回一个ModelAndView实例。该ModelAndView实例将包含两部分内容,一部分为视图相关内容,可以是逻辑视图名称,也可以是具体的View实例;

另一部分则是模型数据,视图渲染过程中将会把这些模式数据合并入最终的视图输出。所以,简单来说,ModelAndView实际上就是一个数据对象。

不过通过该数据对象,我们可以解除具体的Web请求处理Controller与视图渲染之间的紧密耦合,使得两个方面能够独立演化。

为了方便实例化ModelAndView,该类定义了两组参数各异的构造方法,一组使用逻辑视图名称标志视图,一组直接使用View实例标志视图,如下所示:

public ModelAndView(String viewName)

public ModelAndView(String viewName,Map model)

public ModelAndview(String viewName,String modelName,Object mode10bject) public ModelAndView(View view)

public ModelAndView(View view,Map model)

public ModelAndView(View view,String modelName,Object modelobject) 

每组的第一个构造方法只接受视图信息,所以,构造完成后,我们得通过addA110bject(..)或者addobject(…)实例方法,向构造完成的ModelAndView实例添加模型数据;

每组第二个构造方法则可以同时指定视图信息和模型数据信息,一步到位;

如果要添加到模型的只有一个数据对象,那么可以使用每组的第三个构造方法,该构造方法属于第二个构造方法的简化版。

除了以上的构造方法之外,ModelAndView还有一个默认的没有参数的构造方法,如果使用该构造方法实例化对象,那么之后就需要使用其他实例方法来设置视图和模型数据信息了(听起来有点儿像废话哦)


ModelAndView内部提供的属性有下面三个:

	//保存view对象或者视图名
	private Object view;

	//存放模型数据
	private ModelMap model;

    //当前ModelAndView对象内部的view和model数据是否都已经被清空了----方便对象的复用
	private boolean cleared = false;

ModelAndView中的视图信息

ModelAndView可以返回逻辑视图名,或者View实例,如果直接返回了具体的View实例,那么,DispathcerServlet将直接从ModelAndView中获取该View实例并渲染视图,如下所示:

View view=null;
,,,
view=mv.getView();
view.render(mv.getModelInternal(),request,response);

如果返回的是逻辑视图名称,DispatcherServlet将寻求ViewResolver的帮助,根据ModelAndView中的逻辑视图名称获取一个可用的View实例,然后再渲染视图:

View view=null;
,,,
view=resolveViewName(mv.getViewName(),mv.getModelInternal(),locale,request);
view.render(mv.getModelInternal(),request,response);

注意 虽然通过ModelAndView可以保存视图的逻辑名称或者具体的View实现类,但是我们更倾向于使用逻辑视图名来标志视图。这样可以给我们的视图选择带来很大的灵活性,除非必要,尽量不要直接返回具体的View实例。


ModelAndview 中的模型数据

ModelAndView以org.springframework.ui.ModelMap的形式来保持模型数据,通过构造方法传 入的或者通过实例方法添加的模型数据都将添加到这个ModelMap中。至于ModelMap中保持的模型数据将会在视图渲染阶段,由具体的View实现类来获取并使用。

我们需要为添加到ModelAndView的一组或者多组模型数据提供相应的键(Key),以便具体的View实现类可以根据这些键获取具体的模型数据,然后公开给视图模板。通常,模型中的数据对应的键需要与视图模板中的标志符相对应:

基于JSP/JSTL模板的视图实现,通常是将模型数据通过HttpServletRequest的属性(Attribute) 的形式公开给具体的模板。而像基于Velocity之类的通用模板引擎的视图实现,则会将ModelAndView中的模型数据复制到它们自己的数据获取上下文中,比如Velocity的Context。但不管什么视图类型,对应的视图模板都将可以通过添加到ModelAndView的模型数据的键来获取模型数据,并合并到最终的视图输出结果中。


视图定位器ViewResolver

我们已经知道了ViewResolver的主要职责是,根据Controller所返回的ModelAndView中的逻辑 视图名,为DispatcherServlet返回一个可用的View实例。现在是揭开viewResolver如何“尽职” 的时候了。

有ViewResolver的职责为前提,理解甚至于自己声明一个ViewResolver接口变得不再困难。实际上ViewResolver接口定义确实很简单,如下所示:

public interface ViewResolver 
	View resolveViewName(String viewName, Locale locale) throws Exception;

接口实现类只需要根据resolveViewName()方法中以参数形式传入的逻辑视图名(viewName)和当前Locale的值,返回相应的view实例即可。

至于每个ViewResolver实现类如何处理具体的逻辑视图名与具体的View实例之间的对应关系,则因实现类的不同而存在差异。

大部分的ViewResolver实现类,除了org.springframework.web.servlet.view.BeanNameViewResolver是直接实现ViewResolver接口,都直接或者间接继承自
org.springframe work. web.servlet.view.AbstractCachingViewResolver

因为针对每次请求都重新实例化view将可能 为Web应用程序带来性能上的损失,所以Spring MVC在AbstractCachingViewResolver这一继承层 次加入了view实例的缓存功能。AbstractCachingViewResolver默认启用view的缓存功能。对于生 产环境来说,这是合理的默认值。

不过,如果在测试或者开发环境下,我们想即刻反映相应的修改结果,可以通过setCache(false)暂时关闭AbstractCachingViewResolver的缓存功能。

Spring MVC在AbstractCachingViewResolver的基础上为我们提供了一系列的ViewResolver 实现。下面让我们来认识一下它们的庐山直面目

完整继承关系:


AbstractCachingViewResolver

AbstractCachingViewResolver负责完成对已经查询过的视图的缓存,核心方法只有一个为resolveViewName:

public View resolveViewName(String viewName, Locale locale) throws Exception 
        //是否开启了缓存,默认是开启的
		if (!isCache()) 
		    //如果没有开启缓存,那么每次都新创建一个View实例
			return createView(viewName, locale);
		
		else 
		    //尝试从缓存中获取
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			//如果缓存中没有的话,通过双重锁机制确保线程安全
			if (view == null) 
				synchronized (this.viewCreationCache) 
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) 
						//创建一个新的View实例
						view = createView(viewName, locale);
						//如果创建失败了,那么缓存也会记录,避免下次重复尝试创建
						if (view == null && this.cacheUnresolved) 
							view = UNRESOLVED_VIEW;
						
						//加入缓存
						if (view != null) 
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) 
								logger.trace("Cached view [" + cacheKey + "]");
							
						
					
				
			
			//返回view,还是要区分一下不能被解析的情况
			return (view != UNRESOLVED_VIEW ? view : null);
		
	

核心中的核心方法createView调用了loadView,不难想象该方法肯定要由不同的子类去实现:

	protected View createView(String viewName, Locale locale) throws Exception 
		return loadView(viewName, locale);
	
    
    protected abstract View loadView(String viewName, Locale locale) throws Exception;

可用的 ViewResolver 实现类

为了便于理解,我们可以将Spring MVC提供的ViewResolver划分为两类,一类称为“面向单一视图类型的ViewResolver,另一类则称为面向多视图类型的viewResolver。下面是这两类ViewResolver 的详细情况。


1.面向单一视图类型的ViewResolver

该类别ViewResolver的正宗名称应该是UrlBasedViewResolver(它们都直接地或者间接地 继承自该类)。使用该类别的ViewResolver,我们不需要为它们配置具体的逻辑视图名到具体View的映射关系。通常只要指定一下视图模板所在的位置,这些viewResolver就会按照逻辑视图名,抓取相应的模板文件、构造对应的view实例并返回。

之所有又将它们称之为面向单一视图类型的ViewResolver,是因为该类别中,每个具体的ViewResolver实现都只负责一种View类型的映射, ViewResolver与View之间的关系是一比一。

比如,我们之前一直使用的InternalResourceViewResolver,它通常就只负责到指定位置抓取JSP模板文件,并构造InternalResourceView类型的View 实例并返回。

而velocityViewResolver则只关心指定位置的Velocity模板文件(.vm),并会将逻辑 视图名映射到视图模板的文件名,然后构造VelocityView类型的View实例返回,诸如此类。


UrlBasedViewResolver类的核心方法为createView,对父类AbstractCachingViewResolver做了增强:

	@Override
	protected View createView(String viewName, Locale locale) throws Exception 
		//判断当前视图解析器能否解析当前视图名,如果不能直接返回null,表示无法解析
		if (!canHandle(viewName, locale)) 
			return null;
		
		//判断视图名是否以"redirect:"开头,表示重定向请求
		//重定义请求返回的是RedirectView
		//applyLifecycleMethods是调用RedirectView相关初始化方法以及相关后处理
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) 
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
			return applyLifecycleMethods(viewName, view);
		
		// 判断视图名是否以"forward:"开头,表示转发请求
		if (viewName.startsWith(FORWARD_URL_PREFIX)) 
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			return new InternalResourceView(forwardUrl);
		
		//做完增加后,继续走父类的逻辑---父类方法只会去调用loadView
		return super.createView(viewName, locale);
	
	
    //用户设置好当前视图解析器能够解析的视图数组,然后挨个ant匹配判断,有一个匹配上就返回true
    //如果用户没有设置能够解析的视图数组,也默认可以处理
   	protected boolean canHandle(String viewName, Locale locale) 
		String[] viewNames = getViewNames();
		return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
	
    
    	private View applyLifecycleMethods(String viewName, AbstractView view) 
		return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
	

另一大核心方法就是loadView:

	@Override
	protected View loadView(String viewName, Locale locale) throws Exception 
	    //实例化view
		AbstractUrlBasedView view = buildView(viewName);
		//对View进行后处理
		View result = applyLifecycleMethods(viewName, view);
		//checkResource: 检查对应的视图资源是否真的存在,如果不存在,返回false
		return (view.checkResource(locale) ? result : null);
	
   
   
	protected AbstractUrlBasedView buildView(String viewName) throws Exception 
		//UrlBasedViewResolver内部有一个viewClass属性,表示当前UrlBasedViewResolver负责创建哪种view实例
		//这里通过设置好的viewClass,反射实例化一个出来
		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
		//设置视图的完整URL=用户设置的前缀(默认为空)+viewName+用户设置的后缀(默认为空)
		view.setUrl(getPrefix() + viewName + getSuffix());
        //渲染的类型 
		String contentType = getContentType();
		if (contentType != null) 
			view.setContentType(contentType);
		
        //用户可以在配置文件中声明一些通用属性放入当前ViewResolver生成的view实例中
		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());
        
        //是否暴露当前view视图的url路径到模型中去
		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) 
			view.setExposePathVariables(exposePathVariables);
		

		return view;
	

属于该类别的主要ViewResolver实现类为如下几个。

  • InternalResourceviewReBolver。

它是我们使用最多的ViewResolver实现类型,它对应InternalResourceView视图类型的映射,说白了也就是处理JSP模板类型的视图映射。

如果DispatcherServlet在初始化的时候,不能在自己的webApplicationContext中找到至少一个ViewResolver,那么,InternalResourceViewResolver将作为默认的ViewResolver被使用。

  • FreeMarkerViewResolver/VelocityViewReBolver。

FreeMarkerViewResolver和VelocityViewResolver分别负责对应FreeMarkerView和VelocityView类型视图的查找工作,它们将根据逻辑视图名到指定的位置获取对应的模板文件,并构造FreeMarkerView和VelocityView的实例返回给DispatcherServlet使用。

  • JasperReportsViewResolver。

JasperReportsViewResolver只关心根据逻辑视图名到指 定位置查找JasperReport类型模板文件,并返回AbstractJasperReportsView的具体子类 型View实例,例如JasperReportsCsvView或者JasperReportshtmlView等。

  • XsltViewResolver。

只负责根据逻辑视图名查找并返回xsltView类型的View实例。


启用以上这些viewResolver,与使用InternalResourceViewResolver一样简单。

最基本的方法是,使用prefix属性指定模板所在路径,使用suffix属性指定模板文件的后缀名。

这样,在获取逻辑视图名之后,相应的ViewResolver内部就能够根据[prefix]+viewName+[suffix]这样的URL 找到对应的模板文件,并构造对应的view实例而返回了。

以velocityViewResolver的使用为例,至于其他的几个ViewResolver的使用,你基本上就可以“举一反三”了,下面给出了针对VelocityViewResolver的配置代码示例:

<bean id="viewResolver"class="org.springframework.Web.servlet.view.velocity.VelocityViewResolver"> 
<property name="prefix" value="../velocity/"/>
<property name="suffix" value=".vm"/> 
</bean>

现在DispatcherServlet对视图的请求将会由VelocityViewResolver接管,VelocityViewResolver将根据传入的逻辑视图名,到指定目录下查找.vm类型的Velocity模板文件,并构造VelocityView实例返回给DispatcherServlet使用。

就跟我们所说的那样,它只负责到指定位置查找对应Velocity的单一视图类型,而不会返回其他,比如Freemarker视图对应的view实例。

对于这些ViewResolver的具体实现子类来说,套路都是固定的,重写父类的buildView方法,来额外添加一些定制的属性:

  • 当然还有一点就是会将viewClass实例化为当前ViewResolver能够解析处理的


2.面向多视图类型的viewResolver

使用面向单一视图类型的ViewResolver,我们不需要指定明确的逻辑视图名与具体视图之间的映射关系,对应的ViewResolver将自动到指定位置匹配自己所管辖的那种视图模板,并构造具体的View实例。

面向多视图类型的ViewResolver则不然。使用面向多视图类型的ViewResolver,我们需 要通过某种配置方式明确指定逻辑视图名与具体视图之间的映射关系,这可能带来配置上的烦琐。不过,好处是,面向多视图类型的ViewResolver可以顾及多种视图类型的映射管理。如果你的逻辑视图名想要映射到InternalResourceView,那么面向多视图类型的ViewResolver可以做到。如果你的 逻辑视图名想要映射到velocityView,那么,面向多视图类型的ViewResolver也可以做到。相对于只支持单一视图类型映射的情况,面向多视图类型的ViewResolver更加灵活。

面向多视图类型的ViewResolver的主要实现类有三个,它们分别是ResourceBundleViewResolver、XmlViewResolver以及BeanNameViewResolver。

以下是它们的详细情况介绍。

  • ResourceBundleViewResolver。

ResourceBundleViewResolver构建在ResourceBundle上,继 承了ResourceBundle国际化支持的能力,也是所有的ViewResolver实现类中唯一提供视图国际化支持的ViewResolver。

ResourceBundleViewResolver管理的视图的逻辑名称与具体视图的映射关系保存在properties文件中,格式符合Spring的IoC容器的properties配置格式。

ResourceBundleViewResolver内部将通过PropertiesBeanDefinitionReader加载这些配置信息。

之后,根据逻辑视图名查找的操作,实际上也就简化为beanfactory.getBean(viewName)的形式了(当然,实际上要做 的事情会多一些)。

	@Override
	protected View loadView(String viewName, Locale locale) throws Exception 
	   //利用PropertiesBeanDefinitionReader去读取对应的properties文件
	   //然后利用IOC去实例化这些viewBean,对应的beanName就是对应的viewName
		BeanFactory factory = initFactory(locale);
		try 
		//对应的配置文件中已经声明好了相关View和ViewName的映射关系了
			return factory.getBean(viewName, View.class);
		
		catch (NoSuchBeanDefinitionException ex) 
			// Allow for ViewResolver chaining...
			return null;
		
	

使用ResourceBundleViewResolver之前,我们得先将其添加到DispatcherServlet的webApplicationContext中,如下所示:

    <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    </bean>

如果我们没有指定properties配置文件从何处加载的话,ResourceBundleViewResolver默 认将从classpath的根路径加载以views为basename的properties文件,比如views.properties、 views_zh_CN.properties等。

如果我们想改变这种默认加载行为,可以通过setBasename(String) 或者setBasenames(String[])方法来进行变更。

以下是一个典型的ResourceBundleViewResolver使用的properties配置文件内容:

viewTemplate.class=org.springframework.Web.servlet.view.InternalResourceView viewTemplate.(abstract)=true

help/HelpForSomething.(parent)=viewTemplate
help/HelpForSomething.url=/WEB-INF/jsp/help/HelpForSomething.jsp 

hello.class=org.springframework.Web.servlet.view.velocity.VelocityView
hello.url=cn/spring21/simplefx/resources/velocity/hello.vm

#其他视图定义·....·

视图的bean定义主要有两个属性:class和url。

如果我们想要避免每次为同一类型的视图指定某些共同的属性,也可以定义一个模板声明,然后通过parent引用该模板声明。


注意:

如果要在ResourceBundleViewResolver中使用Velocity或者Freemarker之类的通用 模板引擎渲染的视图,那么需要在WebApplicationContext中添加相应的配置,使得视图渲染阶段能够获取模板引擎的支持。

实际上,单独使用VelocityViewResolver或者FreemarkerViewResolver也需要同样的配置。

我们以使用Velocity类型视图的配置为例,(Freemarker类型视图的配置与Velocity类型视图的配置雷同)。在应用程序的WebApplicationContext中,我们添加org.springframework.web. servlet.view.velocity.VelocityConfigurer的配置如下:

<bean id="velocityConfig" class="org.springframework.Web. servlet.view. velocity.VelocityConfigurer"> 
<property name="configLocation" value="/WEB-INF/velocity-config.properties"/> 
</bean>

这样,在视图渲染阶段就可以根据该配置获取一个VelocityEngine进行视图模板与数据的合并(Merge)操作,以便最终输出视图页面。

velocity-config.properties的配置内容,完全就是特定于Velocity的内容了。你可以参考Velocity的 相关文档获取配置参数,这里可以给出一个简单的实例,如下所示:

resource.loader=classpath

classpath.resource.loader.description = Classpath Resource Loader classpath.resource.loader.class=

org. apache. velocity.runtime.resource. loader.ClasspathResourceLoader classpath.resource.loader.path=.

velocimacro.library=

最后,对于Velocity(或者Freemarker)的模板文件,最好像我们给出的配置内容所指定的那样,将它们放入应用程序的classpath中进行加载,而不是依赖于默认的文件系统加载行为。


  • XmlViewResolver。

XmlViewResolver与ResourceBundleViewResolver之间最主要的区 别就是,它们所采用的配置文件格式不同。

ResourceBundleViewResolver按照Spring IoC容器 所接受的properties配置格式配置逻辑视图名与具体视图之间的映射关系,而XmlViewResolver则是按照Spring IoC容器接受的XML配置文件格式来加载映射信息。

与ResourceBundleViewResolver 同样的配置信息,使用xmlViewResolver的话,内容下所示:

<bean name="viewTemplate" class="org.springframework.Web.servlet.view.InternalResourceView" abstract="true">
</bean>

<bean name="help/HelpForSomething" parent="viewTemplate">
<property name="url" value="/WEB-INF/jsp/help/HelpForSomething.jsp"/> 
</bean>

<bean name="hello" class="org.springframework.Web.servlet.view.velocity.VelocityView" p:url="cn/spring21/simplefx/resources/velocity/hello.vm">
</bean> 

XmIViewResolver默认会加载/WEB-INF/views.xml作为配置文件。不过,我们可以在将XmlViewResolver添加到webApplicationContext的时候,根据情况改变这一默认行为,例如:

<bean id="xmlViewResolver" class="org.springframework.Web.servlet.view.XmlViewResolver"> 
<property name="1ocation" value="classpath:views.xm1"/>
</bean>

现在,XmlViewResolver将从Classpath的根路径加载名为views.xml的配置文件。

至于其他配置, 比如Velocity需要的VelocityConfigurer,因为与使用何种viewResolver没有关系,只与是否使 用Velocity作为视图技术有关,所以依然需要根据情况添加到webApplicationcontext中。

注意 XmlViewResolver并不支持视图的国际化(118n)。如果必须对国际化视图给予支持,需要使用ResourceBundleViewResolver。


BeanNameViewResolver

BeanNameViewResolver可以认为是XmlViewResolver的原型版或者简 化版。

使用它,我们可以直接将view实例注册到当前DispatcherServlet所使用的特定的webAppli

以上是关于Spring MVC各组件近距离接触--下--04的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC各组件近距离接触--下下--05

如何用Nearby Service让你的游戏实现近距离联机

近距离3d显示混乱

近距离看GPU计算

近距离看GPU计算

近距离看GPU计算