前言:
当我们访问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文件作为解析视图的配置文件。
FreeMarkerViewResolver、VolocityViewResolver:
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>