ViewResolver 视图解析器

pringMVC 视图解析器

前言

   在前一篇博客中讲了 SpringMVC 的Controller 控制器 ,在这篇博客中将接着介绍一下 SpringMVC 视图解析器。当我们对SpringMVC控制的资源发起请求时,这些请求都会被SpringMVC的DispatcherServlet处理,接着Spring会分析看哪一个HandlerMapping定义的所有请求映射中存在对该请求的最合理的映射。然后通过该HandlerMapping取得其对应的Handler,接着再通过相应的HandlerAdapter处理该Handler。HandlerAdapter在对Handler进行处理之后会返回一个ModelAndView对象。在获得了ModelAndView对象之后,Spring就需要把该View渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是ViewResolver和View。当Handler返回的ModelAndView中不包含真正的视图,只返回一个逻辑视图名称的时候,ViewResolver就会把该逻辑视图名称解析为真正的视图View对象。View是真正进行视图渲染,把结果返回给浏览器的。

ViewResolver 和 View 介绍

SpringMVC 用于处理视图最重要的两个接口是 ViewResolver 和 View 。 ViewResolver 的主要作用是把一个逻辑上的视图名称解析为一个真正的视图, SpringMVC 中用于把 View 对象呈现给客户端的是 View 对象本身,而 ViewResolver 只是把逻辑视图名称解析为对象的 View 对象。 View 接口的主要作用是用于处理视图,然后返回给客户端。

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 支持返回的视图名称中包含 redirect: 前缀,这样就可以支持 URL 在客户端的跳转,如当返回的视图名称是 ”redirect:test.do” 的时候, URLBasedViewResolver 发现返回的视图名称包含 ”redirect:” 前缀,于是把返回的视图名称前缀 ”redirect:” 去掉,取后面的 test.do 组成一个 RedirectView , RedirectView 中将把请求返回的模型属性组合成查询参数的形式组合到 redirect 的 URL 后面,然后调用 HttpServletResponse 对象的 sendRedirect 方法进行重定向。同样 URLBasedViewResolver 还支持 forword: 前缀,对于视图名称中包含 forword: 前缀的视图名称将会被封装成一个 InternalResourceView 对象,然后在服务器端利用 RequestDispatcher 的 forword 方式跳转到指定的地址。使用 UrlBasedViewResolver 的时候必须指定属性 viewClass ,表示解析成哪种视图,一般使用较多的就是 InternalResourceView ,利用它来展现 jsp ,但是当我们使用 JSTL 的时候我们必须使用 JstlView 。下面是一段 UrlBasedViewResolver 的定义,根据该定义,当返回的逻辑视图名称是 test 的时候, UrlBasedViewResolver 将把逻辑视图名称加上定义好的前缀和后缀,即“ /WEB-INF/test.jsp ”,然后新建一个 viewClass 属性指定的视图类型予以返回,即返回一个 url 为“ /WEB-INF/test.jsp ”的 InternalResourceView 对象。

<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 的子类,所以 URLBasedViewResolver 支持的特性它都支持。在实际应用中 InternalResourceViewResolver 也是使用的最广泛的一个视图解析器。那么 InternalResourceViewResolver 有什么自己独有的特性呢?单从字面意思来看,我们可以把 InternalResourceViewResolver 解释为内部资源视图解析器,这就是 InternalResourceViewResolver 的一个特性。 InternalResourceViewResolver 会把返回的视图名称都解析为InternalResourceView 对象, InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL 。比如在 InternalResourceViewResolver 中定义了 prefix=/WEB-INF/ , suffix=.jsp ,然后请求的 Controller 处理器方法返回的视图名称为 test ,那么这个时候 InternalResourceViewResolver 就会把 test 解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword 到 /WEB-INF/test.jsp 。这就是 InternalResourceViewResolver 一个非常重要的特性,我们都知道存放在 /WEB-INF/ 下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在 WEB-INF 目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。下面是一个 InternalResourceViewResolver 的定义,根据该定义当返回的逻辑视图名称是 test 的时候, InternalResourceViewResolver 会给它加上定义好的前缀和后缀,组成“ /WEB-INF/test.jsp ”的形式,然后把它当做一个 InternalResourceView 的 url 新建一个 InternalResourceView对象返回。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/"/>
       <property name="suffix" value=".jsp"></property>
    </bean>

 

XmlViewResolver :它继承自 AbstractCachingViewResolver 抽象类,所以它也是支持视图缓存的。 XmlViewResolver 需要给定一个 xml 配置文件,该文件将使用和 Spring 的 bean 工厂配置文件一样的 DTD 定义,所以其实该文件就是用来定义视图的 bean 对象的。在该文件中定义的每一个视图的 bean 对象都给定一个名字,然后 XmlViewResolver 将根据 Controller 处理器方法返回的逻辑视图名称到 XmlViewResolver 指定的配置文件中寻找对应名称的视图 bean 用于处理视图。该配置文件默认是 /WEB-INF/views.xml 文件,如果不使用默认值的时候可以在 XmlViewResolver 的 location 属性中指定它的位置。 XmlViewResolver 还实现了 Ordered 接口,因此我们可以通过其 order 属性来指定在 ViewResolver 链中它所处的位置, order 的值越小优先级越高。以下是使用 XmlViewResolver 的一个示例:

( 1 )在 SpringMVC 的配置文件中加入 XmlViewResolver 的 bean 定义。使用 location 属性指定其配置文件所在的位置, order 属性指定当有多个 ViewResolver 的时候其处理视图的优先级。关于 ViewResolver 链的问题将在后续内容中讲到。

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

 

( 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("/xmlViewResolver")
    public String testXmlViewResolver() {
       return "internalResource";
    }

 

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

BeanNameViewResolver :这个视图解析器跟 XmlViewResolver 有点类似,也是通过把返回的逻辑视图名称去匹配定义好的视图 bean 对象。不同点有二,一是 BeanNameViewResolver 要求视图 bean 对象都定义在 Spring 的 application context 中,而 XmlViewResolver 是在指定的配置文件中寻找视图 bean 对象,二是 BeanNameViewResolver 不会进行视图缓存。看一个例子,在 SpringMVC 的配置文件中定义了一个 BeanNameViewResolver 视图解析器和一个 id 为 test 的InternalResourceview bean 对象。

<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>

 

这样当返回的逻辑视图名称是 test 的时候,就会解析为上面定义好 id 为 test 的 InternalResourceView 。

ResourceBundleViewResolver :它和 XmlViewResolver 一样,也是继承自 AbstractCachingViewResolver ,但是它缓存的不是视图,这个会在后面有说到。和 XmlViewResolver 一样它也需要有一个配置文件来定义逻辑视图名称和真正的 View 对象的对应关系,不同的是 ResourceBundleViewResolver 的配置文件是一个属性文件,而且必须是放在 classpath 路径下面的,默认情况下这个配置文件是在 classpath 根目录下的 views.properties 文件,如果不使用默认值的话,则可以通过属性 baseName 或 baseNames 来指定。 baseName 只是指定一个基名称, Spring 会在指定的 classpath 根目录下寻找以指定的 baseName 开始的属性文件进行 View 解析,如指定的 baseName 是 base ,那么 base.properties 、 baseabc.properties 等等以 base 开始的属性文件都会被 Spring 当做 ResourceBundleViewResolver 解析视图的资源文件。 ResourceBundleViewResolver 使用的属性配置文件的内容类似于这样:

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

 

在这个配置文件中我们定义了两个 InternalResourceView 对象,一个的名称是 resourceBundle,对应 URL 是 /index.jsp ,另一个名称是 test ,对应的 URL 是 /test.jsp 。从这个定义来看我们可以知道 resourceBundle 是对应的视图名称,使用 resourceBundle.(class) 来指定它对应的视图类型, resourceBundle.url 指定这个视图的 url 属性。会思考的读者看到这里可能会有这样一个问题:为什么 resourceBundle 的 class 属性要用小括号包起来,而它的 url 属性就不需要呢?这就需要从 ResourceBundleViewResolver 进行视图解析的方法来说了。 ResourceBundleViewResolver还是通过 bean 工厂来获得对应视图名称的视图 bean 对象来解析视图的。那么这些 bean 从哪里来呢?就是从我们定义的 properties 属性文件中来。在 ResourceBundleViewResolver 第一次进行视图解析的时候会先 new 一个 BeanFactory 对象,然后把 properties 文件中定义好的属性按照它自身的规则生成一个个的 bean 对象注册到该 BeanFactory 中,之后会把该 BeanFactory 对象保存起来,所以 ResourceBundleViewResolver 缓存的是 BeanFactory ,而不是直接的缓存从 BeanFactory 中取出的视图 bean 。然后会从 bean 工厂中取出名称为逻辑视图名称的视图 bean 进行返回。接下来就讲讲 Spring 通过 properties 文件生成 bean 的规则。它会把 properties 文件中定义的属性名称按最后一个点“ . ”进行分割,把点前面的内容当做是 bean 名称,点后面的内容当做是 bean 的属性。这其中有几个特别的属性, Spring 把它们用小括号包起来了,这些特殊的属性一般是对应的 attribute ,但不是 bean 对象所有的 attribute 都可以这样用。其中 (class) 是一个,除了 (class) 之外,还有 (scope) 、 (parent) 、 (abstract) 、 (lazy-init) 。而除了这些特殊的属性之外的其他属性, Spring 会把它们当做 bean 对象的一般属性进行处理,就是 bean 对象对应的 property 。所以根据上面的属性配置文件将生成如下两个 bean 对象:

<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>

 

从 ResourceBundleViewResolver 使用的配置文件我们可以看出,它和 XmlViewResolver 一样可以解析多种不同类型的 View ,因为它们的 View 是通过配置的方式指定的,这也就意味着我们可以指定 A 视图是 InternalResourceView , B 视图是 JstlView 。

来看下面这个一个例子,我在 SpringMVC 的配置文件中定义了一个 ResourceBundleViewResolver 对象,指定其 baseName 为 views ,然后 order 为 1 。

<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
       <property name="basename" value="views"/>
       <property name="order" value="1"/>
    </bean>

 

我在 classpath 的根目录下有两个属性文件,一个是 views.properties ,一个是 views_abc.properties ,它们的内容分别如下:

views.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

 

views_abc.properties :

abc.(class)=org.springframework.web.servlet.view.InternalResourceView
abc.url=/abc.jsp

 

定义了如下这样一个 Controller ,它有三个处理器方法。

@Controller
@RequestMapping("/mytest")
public class MyController {
    @RequestMapping("resourceBundle")
    public String resourceBundle() {
       return "resourceBundle";
    }

    @RequestMapping("testResourceBundle")
    public String testResourceBundle() {
       return "test";
    }

    @RequestMapping("abc")
    public String abc() {
       return "abc";
    }

}

 

那么当我们请求 /mytest/resourceBundle.do 的时候, ResourceBundleViewResolver 会首先尝试着来解析该视图,这里 Controller 处理器方法返回的逻辑视图名称是 resourceBundle , ResourceBundleViewResolver 按照上面提到的解析方法进行解析,这个时候它发现它是可以解析的,然后就返回了一个 url 为 /index.jsp 的 InternalResourceView 对象。同样,请求 /mytest/testResourceBundle.do 返回的逻辑视图 test 和 /mytest/abc.do 返回的逻辑视图 abc 它都可以解析。当我们把 basename 指定为包的形式,如“ com.tiantian.views ”,的时候 Spring 会按照点“ . ”划分为目录的形式,到 classpath 相应目录下去寻找 basename 开始的配置文件,如上面我们指定 basename 为“ com.tiantian.views ”,那么 spring 就会到 classpath 下的 com/tiantian 目录下寻找文件名以 views 开始的 properties 文件作为解析视图的配置文件。

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

 

视图解析器链

       在 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>
posted @ 2017-03-02 16:10  superAnny  阅读(9600)  评论(1编辑  收藏  举报