前言:

当我们访问SpringMVC控制的资源的时候,由DispatcherServlet处理,然后Spring的HandlerMapping在所有映射中
查找匹配的映射,交给对应的Handler,再通过相应的HandlerAdpter处理Handler,处理完成后返回ModelAndView对象。然
后Spring通过ViewResolver和View把该View渲染给用户,即返回给浏览器。如果ModelAndView只包含逻辑视图名的时候,
ViewResolver负责解析逻辑视图名为真正的View对象,View对象进行真正的视图渲染。



ViewResolver和View介绍:

SpringMVC视图解析器的两个重要的接口:ViewResolver和View。ViewResolver负责吧逻辑视图名解析为View视图对象,
View对象本身把自己渲染给客户端,呈现给用户。

AbstractCachingViewResolver

AbstractCachingViewResolver 是一个抽象类,会把曾经解析过的视图以Map的形式放到缓存中,每次解析视图之前就会从
缓存中取,没有就会新建视图并存入,并返回

UrlBasedViewResolver

URLBasedViewResolver 继承了 AbstractCachingViewResolver 简单实现了 ViewResolver。主要用拼接URL的方式
来解析视图。需要指定prefix前缀和suffix后缀,比如:viewName为test,prefix为/jsp/,suffix为.jsp,拼接后的视图url
为/jsp/test.jsp。它支持viewName包含redirect:和forward:。解析时会把前缀redirect:和forward:去掉。前者比如:
redirect:test,就会处理成test.do,生产RedirectView对象,调用HttpServletResponse的sendRedirect方法来重定向,
RedirectView对象会把视图模型中的属性会作为重定向的参数拼在url后面。后者比如:forward:test.do,该视图名称会被封装成
InternalResourceView对象,调用RequestDispatcher的forward方法跳转。
    URLBasedViewResolver 必须给它指定viewClass来表明解析类型,一般是InternalResourceView,如果是Jsp中用到Jstl
则需用JstlView。
    下面为配置示例:
<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

InternalResourceViewResolver 称为内部资源解析器,是URLBasedViewResolver的子类。它有一个
URLBasedViewResolver没有的特性:它先把视图名封装成InternalResourceView对象,InternalResourceView对象会把
视图模型数据放到HttpServletRequest属性中,在调用RequestDispatcher的forward方法,跳到jsp页面。因为为了安全,
会把jsp页面放到WEB-INF下,WEB-INF下的资源是不能被request到的。
    下面为配置示例:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
   <property name="prefix" value="/WEB-INF/"/>  
   <property name="suffix" value=".jsp"></property>  
</bean>

XmlViewResolver

XmlResolver 继承了AbstractCachingViewResolver,支持视图缓存;也实现了Ordered接口,可以制定优先级,order
值越小,优先级越高。
    XmlResolver 需要在SpringMVC配置文件中加入其定义,它还需要一个自己的配置文件,来定义它的各种视图,它的配置文件
的DTD和Spring bean工厂的DTD是一样的。该配置文件默认是/WEB-INF/views.xml文件,如果不使用默认值的时候可以在
XmlViewResolver的location属性中指定它的位置。
   
   (1)在SpringMVC的配置文件中加入XmlViewResolver的bean定义。使用location属性指定其配置文件所在的位置,order
属性指定当有多个ViewResolver的时候其处理视图的优先级。关于ViewResolver链的问题将在后续内容中讲到。
    Spring的配置文件要加入的配置:
<bean class="org.springframework.web.servlet.view.XmlViewResolver">  
   <property name="location" value="/WEB-INF/views.xml"/>  
   <property name="order" value="1"/>  
</bean>  
    
    views.xml示例:
   (2)在XmlViewResolver对应的配置文件中配置好所需要的视图定义。在下面的代码中我们就配置了一个名为internalResource
的InternalResourceView,其url属性为“/index.jsp”。

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <bean id="internalResource" class="org.springframework.web.servlet.view.InternalResourceView">  
       <property name="url" value="/index.jsp"/>  
    </bean>  
</beans>  
    
   (3)定义一个返回的逻辑视图名称为在XmlViewResolver配置文件中定义的视图名称——internalResource。
    @RequestMapping("/testXmlViewResolver")  
    public String testXmlViewResolver() {  
        return "internalResource";  
    }

    (4)这样当我们访问到上面定义好的testXmlViewResolver处理器方法的时候返回的逻辑视图名称为“internalResource”
,这时候Spring就会到定义好的views.xml中寻找id或name为“internalResource”的bean对象予以返回,这里Spring找到的
是一个url为“/index.jsp”的InternalResourceView对象。

BeanNameViewResolver

BeanNamedViewResolver 跟XMLViewResolver有点类似,通过视图名查找配置文件,定义视图对象。
    不同点一:BeanNamedViewResolver不能指定配置文件,只能在Spring配置文件中配置。
    不同点二:BeanNamedViewResolver不能进行视图缓存
    
    配置示例:
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">  
   <property name="order" value="1"/>  
</bean>  
  
<bean id="test" class="org.springframework.web.servlet.view.InternalResourceView">  
   <property name="url" value="/index.jsp"/>  
</bean>

ResourceBundleViewResolver:

ResourceBundleViewResolver 首先要在SpringMVC配置文件中配置,如下:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">  
   <property name="basename" value="views"/>  
   <property name="order" value="1"/>  
</bean>  
    basename 属性的作用:指定属性文件的前面的名称,上面的配置会加载classpath下的像views、views_a、等等,这些以
views开头的properties视图文件。视图文件必须在classpath下。

    properties示例:

resourceBundle.(class)=org.springframework.web.servlet.view.InternalResourceView  
resourceBundle.url=/index.jsp  
test.(class)=org.springframework.web.servlet.view.InternalResourceView  
test.url=/test.jsp  

    class为什么会加上括号,Spring会在属性文件里给几个关键字加上括号:(class)、(abstract)、(scope)、(parent)、
(lazy-init),除了这些,就会作为普通的属性。
    Spring在第一次试图解析的时候,会创建一个BeanFactory并缓存,而不是缓存Bean。然后会把上面示例的配置,.(class)
前面的会作为beanName,url作为属性创建两个视图bean并注册到BeanFactory中。
    
    如同下面XML配置:
<bean id="resourceBundle" class="org.springframework.web.servlet.view.InternalResourceView">  
   <property name="url" value="/index.jsp"/>  
</bean>  
  
<bean id="test" class="org.springframework.web.servlet.view.InternalResourceView">  
   <property name="url" value="/test.jsp"/>  
</bean>  
   上面的配置可以配置多个View。
   我们把basename指定为包的形式,如“com.tiantian.views”,的时候Spring会按照点“.”划分为目录的形式,到classpath
相应目录下去寻找basename开始的配置文件,如上面我们指定basename为“com.tiantian.views”,那么spring就会到
classpath下的com/tiantian目录下寻找文件名以views开始的properties文件作为解析视图的配置文件。

FreeMarkerViewResolverVolocityViewResolver

FreeMarkerViewResolver、VolicityViewResolver 这两个相似。
FreeMarkerViewResolver的viewClass不需要指定,已经被定为FreeMarkerView。如下:

<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的实现。
Spring的实现为FreeMarkerConfigurer。我们最简单的配置就是配置一个templateLoaderPath,告诉Spring应该到哪里寻找
FreeMarker的模板文件。支持classpath:和file:path前缀。多个路径需要templateLoaderPaths指定。
    如:
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">  
   <property name="templateLoaderPath" value="/WEB-INF/freemarker/template"/>  
</bean>  

@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就可以利用该模板文件进行视图的渲染了。所以访问结果应该如下所示:



视图解析器链:

在SpringMVC中可以同时定义多个ViewResolver视图解析器,然后它们会组成一个ViewResolver链。当Controller处理器方
法返回一个逻辑视图名称后,ViewResolver链将根据其中ViewResolver的优先级来进行处理。所有的ViewResolver都实现了
Ordered接口,在Spring中实现了这个接口的类都是可以排序的。在ViewResolver中是通过order属性来指定顺序的,默认都是最大
值。所以我们可以通过指定ViewResolver的order属性来实现ViewResolver的优先级,order属性是Integer类型,order越小,
对应的ViewResolver将有越高的解析视图的权利,所以第一个进行解析的将是ViewResolver链中order值最小的那个。当一个
ViewResolver在进行视图解析后返回的View对象是null的话就表示该ViewResolver不能解析该视图,这个时候如果还存在其他
order值比它大的ViewResolver就会调用剩余的ViewResolver中的order值最小的那个来解析该视图,依此类推。当
ViewResolver在进行视图解析后返回的是一个非空的View对象的时候,就表示该ViewResolver能够解析该视图,那么视图解析这一
步就完成了,后续的ViewResolver将不会再用来解析该视图。当定义的所有ViewResolver都不能解析该视图的时候,Spring就会抛
出一个异常。
    基于Spring支持的这种ViewResolver链模式,我们就可以在SpringMVC应用中同时定义多个ViewResolver,给定不同的
order值,这样我们就可以对特定的视图特定处理,以此来支持同一应用中有多种视图类型。
注意:像InternalResourceViewResolver这种能解析所有的视图,即永远能返回一个非空View对象的ViewResolver一定要把它
放在ViewResolver链的最后面。

<bean class="org.springframework.web.servlet.view.XmlViewResolver">  
   <property name="location" value="/WEB-INF/views.xml"/>  
   <property name="order" value="1"/>  
</bean>  
  
<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>