请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于返回其他类型(String、Model、View等)的处理方法,Spring MVC也会在内部将它们转换成 ModelAndView 对象,它是包含了逻辑名和模型对象的视图。
SpringMVC 借助视图解析器(ViewResolver) 得到最终的视图(View)对象。最终的视图可以使是 JSP,也可能是Excel、JFreeChart等各种形式的视图。对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器的工作重点聚焦在生产模型上,从而实现 MVC 的充分解耦。
SpringMVC的视图解析流程:
- 调用目标方法,SpringMVC将目标方法返回的String、View、ModelMap或是ModelAndView都转换为一个ModelAndView对象;
- 然后通过视图解析器(ViewResolver)对ModelAndView对象中的View对象进行解析,将该逻辑视图View对象解析为一个物理视图View对象;
- 最后调用物理视图View对象的render()方法进行视图渲染,得到响应结果。
一、视图
视图的作用是渲染模型数据,将模型里的数据以某种形式展现给客户。为了实现视图模型和具体实现技术的解耦,Spring在 org.springframework.web.servlet 中定义了高度解耦的 View 接口。
视图对象由视图解析器负责实例化。由于他们是无状态的,所以是线程安全的。
常用的视图解析器:
大类 | 视图类型 | 说明 |
---|---|---|
URL视资源图 | InternalResourceView | 将JSP或者其它资源封装成一个视图,是 InternalResourceViewResolver 默认使用的视图实现类 |
JstlView | 如果JSP文件中使用了JSTL标签则需要使用该视图 | |
文档视图 | AbstractExcelView | Excel 文档视图的抽象类。该视图类基于 POI 构造 Excel 文档 |
AbstractPdfView | PDF 文档视图的抽象类。该视图类基于 IText 构造 PDF 文档 | |
报表视图 | ConfigurableJsperReportsView | 几个使用JsperReports报表技术的视图 |
JsperReportsCsvView | ||
JsperReportsMultiFormatView | ||
JsperReportsHtmlView | ||
JsperReportsPdfView | ||
JsperReportsXlsView | ||
JSON视图 | MappingJacksonJsonView | 将模型数据通过Jackson开源框架的 ObjectMapper 以JSON 方式输出 |
二、视图解析器
ViewResolver的主要作用是把一个逻辑上的视图名称解析为一个真正的视图。
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring WEB上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。程序员可以选择一种视图解析器或混用多种视图解析器。可以通过order属性指定解析器的优先顺序,order越小优先级越高,SpringMVC会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException异常。
Spring为我们提供了非常多的视图解析器,下面将列举一些视图解析器:
- AbstractCachingViewResolver:这是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。
UrlBasedViewResolver:它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。如prefix=/WEB-INF/jsps/,suffix=.jsp,返回的视图名称viewName=test/indx,则UrlBasedViewResolver解析出来的视图URL就是/WEB-INF/jsps/test/index.jsp。默认的prefix和suffix都是空串。
注意:使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。下面是一段UrlBasedViewResolver的定义,根据该定义,当返回的逻辑视图名称是test的时候,UrlBasedViewResolver将把逻辑视图名称加上定义好的前缀和后缀,即“/WEB-INF/test.jsp”,然后新建一个viewClass属性指定的视图类型予以返回,即返回一个url为“/WEB-INF/test.jsp”的InternalResourceView对象。
URLBasedViewResolver支持支持URL在客户端的跳转,只需在返回视图名称中加上redirect:或forword:前缀。
- 如当返回的视图名称是”redirect:test.do”的时候,URLBasedViewResolver发现返回的视图名称包含”redirect:”前缀,于是把返回的视图名称前缀”redirect:”去掉,取后面的test.do组成一个RedirectView,RedirectView中将把请求返回的模型属性组合成查询参数的形式组合到redirect的URL后面,然后调用HttpServletResponse对象的sendRedirect方法进行重定向。
- 对于视图名称中包含forword:前缀的视图名称将会被封装成一个InternalResourceView对象,然后在服务器端利用RequestDispatcher的forword方式跳转到指定的地址。
<bean
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>
InternalResourceViewResolver:它是 UrlBasedViewResolver的子类,它将InternalResourceView作为缺省的View类,如果当前classpath中有jstl的jar包时则使用JstlView作为缺省的view来渲染结果。因此以下使用InternalResourceViewResolver的定义应该和之前使用UrlBasedViewResolver定义的viewResolver的作用相同。
除此之外,InternalResourceViewResolver还提供了
alwaysInclude属性来要求返回的结果使用include方式而不是forward方式
exposeContextBeansAsAttributes属性以将当前spring 环境中的 beans作为request attritbutes来暴露到页面上。
exposedContextBeanNames属性来限制能够暴露到页面上的spring bean的名称列表。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"></property>
</bean>
若希望直接响应通过SpringMVC渲染的页面,可以使用mvc:view-controller
FreeMarkerViewResolver、VolocityViewResolver:这两个视图解析器都是UrlBasedViewResolver的子类。FreeMarkerViewResolver会把Controller处理方法返回的逻辑视图解析为FreeMarkerView,而VolocityViewResolver会把返回的逻辑视图解析为VolocityView。因为这两个视图解析器类似,所以这里我就只挑FreeMarkerViewResolver来做一个简单的讲解。FreeMarkerViewResolver和VilocityViewResolver都继承了UrlBasedViewResolver。
对于FreeMarkerViewResolver而言,它会按照UrlBasedViewResolver拼接URL的方式进行视图路径的解析。但是使用FreeMarkerViewResolver的时候不需要我们指定其viewClass,因为FreeMarkerViewResolver中已经把viewClass定死为FreeMarkerView了。
我们先在SpringMVC的配置文件里面定义一个FreeMarkerViewResolver视图解析器,并定义其解析视图的order顺序为1。
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="prefix" value="fm_"/>
<property name="suffix" value=".ftl"/>
<property name="order" value="1"/>
</bean>
那么当我们请求的处理器方法返回一个逻辑视图名称viewName的时候,就会被该视图处理器加上前后缀解析为一个url为“fm_viewName.ftl”的FreeMarkerView对象。对于FreeMarkerView我们需要给定一个FreeMarkerConfig的bean对象来定义FreeMarker的配置信息。FreeMarkerConfig是一个接口,Spring已经为我们提供了一个实现,它就是FreeMarkerConfigurer。我们可以通过在SpringMVC的配置文件里面定义该bean对象来定义FreeMarker的配置信息,该配置信息将会在FreeMarkerView进行渲染的时候使用到。对于FreeMarkerConfigurer而言,我们最简单的配置就是配置一个templateLoaderPath,告诉Spring应该到哪里寻找FreeMarker的模板文件。这个templateLoaderPath也支持使用“classpath:”和“file:”前缀。当FreeMarker的模板文件放在多个不同的路径下面的时候,我们可以使用templateLoaderPaths属性来指定多个路径。在这里我们指定模板文件是放在“/WEB-INF/freemarker/template”下面的。
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/template"/>
</bean>
接下来我们定义如下一个Controller:
@Controller
@RequestMapping("/mytest")
public class MyController {
@RequestMapping("freemarker")
public ModelAndView freemarker() {
ModelAndView mav = new ModelAndView();
mav.addObject("hello", "andy");
mav.setViewName("freemarker");
return mav;
}
}
由上面的定义我们可以看到这个Controller的处理器方法freemarker返回的逻辑视图名称是“freemarker”。那么如果我们需要把该freemarker视图交给FreeMarkerViewResolver来解析的话,我们就需要根据上面的定义,在模板路径下定义视图对应的模板,即在“/WEB-INF/freemarker/template”目录下建立fm_freemarker.ftl模板文件。这里我们定义其内容如下:
<html>
<head>
<title>FreeMarker</title>
</head>
<body>
<b>Hello World</b>
<font color="red">Hello World!</font>
${hello}
</body>
</html>
经过上面的定义当我们访问/mytest/freemarker.do的时候就会返回一个逻辑视图名称为“freemarker”的ModelAndView对象,根据定义好的视图解析的顺序,首先进行视图解析的是FreeMarkerViewResolver,这个时候FreeMarkerViewResolver会试着解析该视图,根据它自身的定义,它会先解析到该视图的URL为fm_freemarker.ftl,然后它会看是否能够实例化该视图对象,即在定义好的模板路径下是否有该模板存在,如果有则返回该模板对应的FreeMarkerView。在这里的话/WEB-INF/freemarker/template目录下是存在模板文件fm_freemarker.ftl的,所以会返回一个url为fm_freemarker.ftl的FreeMarkerView对象。接着FreeMarkerView就可以利用该模板文件进行视图的渲染了。
- BeanNameViewResolver:将逻辑视图名解析为一个Bean,Bean的id为逻辑视图名(自定义视图解析器)
- JasperReportsViewResolver:JasperReports是一个基于java的开源报表工具,该解析器将视图名解析为报表文件对应的URL
注意:我们可以选择一种解析器或者多种混用,每个解析器都实现了Ordered接口并开放出一个order属性,可以通过order属性指定解析器的优先顺序,order越小优先级越高。常用的放后边,不常用的放前边。