2018-07-26 15:39 更新
正如在Spring MVC 控制器的实现一节中所讨论的,Spring MVC中所有控制器的处理器方法都必须返回一个逻辑视图的名字,无论是显式返回(比如返回一个String
、View
或者ModelAndView
)还是隐式返回(比如基于约定的返回)。Spring中的视图由一个视图名标识,并由视图解析器来渲染。Spring有非常多内置的视图解析器。下表列出了大部分,表后也给出了一些例子。
表21.3 视图解析器
视图解析器 | 描述 |
---|---|
AbstractCachingViewResolver |
一个抽象的视图解析器类,提供了缓存视图的功能。通常视图在能够被使用之前需要经过准备。继承这个基类的视图解析器即可以获得缓存视图的能力。 |
XmlViewResolver |
视图解析器接口ViewResolver 的一个实现,该类接受一个XML格式的配置文件。该XML文件必须与Spring XML的bean工厂有相同的DTD。默认的配置文件名是/WEB-INF/views.xml 。 |
ResourceBundleViewResolver |
视图解析器接口ViewResolver 的一个实现,采用bundle根路径所指定的ResourceBundle 中的bean定义作为配置。一般bundle都定义在classpath路径下的一个配置文件中。默认的配置文件名为views.properties 。 |
UrlBasedViewResolver |
ViewResolver 接口的一个简单实现。它直接使用URL来解析到逻辑视图名,除此之外不需要其他任何显式的映射声明。如果你的逻辑视图名与你真正的视图资源名是直接对应的,那么这种直接解析的方式就很方便,不需要你再指定额外的映射。 |
InternalResourceViewResolver |
UrlBasedViewResolver 的一个好用的子类。它支持内部资源视图(具体来说,Servlet和JSP)、以及诸如JstlView 和TilesView 等类的子类。You can specify the view class for all views generated by this resolver by using setViewClass(..) 。更多的细节,请见UrlBasedViewResolver 类的java文档。 |
VelocityViewResolver / FreeMarkerViewResolver |
UrlBasedViewResolver 下的实用子类,支持Velocity视图VelocityView (Velocity模板)和FreeMarker视图FreeMarkerView 以及它们对应子类。 |
ContentNegotiatingViewResolver |
视图解析器接口ViewResolver 的一个实现,它会根据所请求的文件名或请求的Accept 头来解析一个视图。更多细节请见Spring MVC 内容协商视图解析器一小节。 |
我们可以举个例子,假设这里使用的是JSP视图技术,那么我们可以使用一个基于URL的视图解析器UrlBasedViewResolver
。这个视图解析器会将URL解析成一个视图名,并将请求转交给请求分发器来进行视图渲染。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
若返回一个test
逻辑视图名,那么该视图解析器会将请求转发到RequestDispatcher
,后者会将请求交给/WEB-INF/jsp/test.jsp
视图去渲染。
如果需要在应用中使用多种不同的视图技术,你可以使用ResourceBundleViewResolver
:
<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
<property name="defaultParentView" value="parentView"/>
</bean>
ResourceBundleViewResolver
会检索由bundle根路径下所配置的ResourceBundle
,对于每个视图而言,其视图类由[viewname].(class)
属性的值指定,其视图url由[viewname].url
属性的值指定。下一节将详细讲解视图技术,你可以在那里找到更多例子。你还可以看到,视图还允许有基视图,即properties文件中所有视图都“继承”的一个文件。通过继承技术,你可以为众多视图指定一个默认的视图基类。
AbstractCachingViewResolver
的子类能够缓存已经解析过的视图实例。关闭缓存特性也是可以的,只需要将cache
属性设置为false
即可。另外,如果实在需要在运行时刷新某个视图(比如修改了Velocity模板时),你可以使用removeFromCache(String viewName, Locale loc)
方法。`
2018-07-26 14:11 更新
Spring支持同时使用多个视图解析器。因此,你可以配置一个解析器链,并做更多的事比如,在特定条件下覆写一个视图等。你可以通过把多个视图解析器设置到应用上下文(application context)中的方式来串联它们。如果需要指定它们的次序,那么设置order
属性即可。请记住,order属性的值越大,该视图解析器在链中的位置就越靠后。
在下面的代码例子中,视图解析器链中包含了两个解析器:一个是InternalResourceViewResolver
,它总是自动被放置在解析器链的最后;另一个是XmlViewResolver
,它用来指定Excel视图。InternalResourceViewResolver
不支持Excel视图。
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="order" value="1"/>
<property name="location" value="/WEB-INF/views.xml"/>
</bean>
<!-- in views.xml -->
<beans>
<bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>
如果一个视图解析器不能返回一个视图,那么Spring会继续检查上下文中其他的视图解析器。此时如果存在其他的解析器,Spring会继续调用它们,直到产生一个视图返回为止。如果最后所有视图解析器都不能返回一个视图,Spring就抛出一个ServletException
。
视图解析器的接口清楚声明了,一个视图解析器是_可以_返回null值的,这表示不能找到任何合适的视图。并非所有的视图解析器都这么做,但是也存在不得不如此的场景,即解析器确实无法检测对应的视图是否存在。比如,InternalResourceViewResolver
在内部使用了RequestDispatcher
,并且进入分派过程是检测一个JSP视图是否存在的唯一方法,但这个过程仅可能发生唯一一次。同样的VelocityViewResolver
和部分其他的视图解析器也存在这样的情况。具体的请查阅某个特定的视图解析器的Java文档,看它是否会report不存在的视图。因此,如果不把InternalResourceViewResolver
放置在解析器链的最后,将可能导致解析器链无法完全执行,因为InternalResourceViewResolver
永远都会 返回一个视图。
2018-07-26 14:12 更新
如前所述,控制器通常都会返回一个逻辑视图名,然后视图解析器会把它解析到一个具体的视图技术上去渲染。对于一些可以由Servlet或JSP引擎来处理的视图技术,比如JSP等,这个解析过程通常是由InternalResourceViewResolver
和InternalResourceView
协作来完成的,而这通常会调用Servlet的APIRequestDispatcher.forward(..)
方法或RequestDispatcher.include(..)
方法,并发生一次内部的转发(forward)或引用(include)。而对于其他的视图技术,比如Velocity、XSLT等,视图本身的内容是直接被写回响应流中的。
有时,我们想要在视图渲染之前,先把一个HTTP重定向请求发送回客户端。比如,当一个控制器成功地接受到了POST
过来的数据,而响应仅仅是委托另一个控制器来处理(比如一次成功的表单提交)时,我们希望发生一次重定向。在这种场景下,如果只是简单地使用内部转发,那么意味着下一个控制器也能看到这次POST
请求携带的数据,这可能导致一些潜在的问题,比如可能会与其他期望的数据混淆,等。此外,另一种在渲染视图前对请求进行重定向的需求是,防止用户多次提交表单的数据。此时若使用重定向,则浏览器会先发送第一个POST
请求;请求被处理后浏览器会收到一个重定向响应,然后浏览器直接被重定向到一个不同的URL,最后浏览器会使用重定向响应中携带的URL发起一次GET
请求。因此,从浏览器的角度看,当前所见的页面并不是POST
请求的结果,而是一次GET
请求的结果。这就防止了用户因刷新等原因意外地提交了多次同样的数据。此时刷新会重新GET
一次结果页,而不是把同样的POST
数据再发送一遍。
强制重定向的一种方法是,在控制器中创建并返回一个Spring重定向视图RedirectView
的实例。它会使得DispatcherServlet
放弃使用一般的视图解析机制,因为你已经返回一个(重定向)视图给DispatcherServlet
了,所以它会构造一个视图来满足渲染的需求。紧接着RedirectView
会调用HttpServletResponse.sendRedirect()
方法,发送一个HTTP重定向响应给客户端浏览器。
如果你决定返回RedirectView
,并且这个视图实例是由控制器内部创建出来的,那我们更推荐在外部配置重定向URL然后注入到控制器中来,而不是写在控制器里面。这样它就可以与视图名一起在配置文件中配置。关于如何实现这个解耦,请参考 重定向前缀——redirect:一小节。
模型中的所有属性默认都会考虑作为URI模板变量被添加到重定向URL中。剩下的其他属性,如果是基本类型或者基本类型的集合或数组,那它们将被自动添加到URL的查询参数中去。如果model是专门为该重定向所准备的,那么把所有基本类型的属性添加到查询参数中可能是我们期望那个的结果。但是,在包含注解的控制器中,model可能包含了专门作为渲染用途的属性(比如一个下拉列表的字段值等)。为了避免把这样的属性也暴露在URL中,@RequestMapping
方法可以声明一个RedirectAttributes
类型的方法参数,用它来指定专门供重定向视图RedirectView
取用的属性。如果重定向成功发生,那么RedirectAttributes
对象中的内容就会被使用;否则则使用模型model中的数据。
RequestMappingHandlerAdapter
提供了一个"ignoreDefaultModelOnRedirect"
标志。它被用来标记默认Model
中的属性永远不应该被用于控制器方法的重定向中。控制器方法应该声明一个RedirectAttributes
类的参数。如果不声明,那就没有参数被传递到重定向的视图RedirectView
中。在MVC命名空间或MVC Java编程配置方式中,为了维持向后的兼容性,这个标志都仍被保持为false
。但如果你的应用是一个新的项目,那么我们推荐把它的值设置成true
。
请注意,当前请求URI中的模板变量会在填充重定向URL的时候自动对应用可见,而不需要显式地在Model
或RedirectAttributes
中再添加属性。请看下面的例子:
@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
// ...
return "redirect:files/{path}";
}
另外一种向重定向目标传递数据的方法是通过 闪存属性(Flash Attributes)。与其他重定向属性不同,flash属性是存储在HTTP session中的(因此不会出现在URL中)。更多内容,请参考 21.6 使用闪存属性一节。
尽管使用RedirectView
来做重定向能工作得很好,但如果控制器自身还是需要创建一个RedirectView
,那无疑控制器还是了解重定向这么一件事情的发生。这还是有点不尽完美,不同范畴的耦合还是太强。控制器其实不应该去关心响应会如何被渲染。In general it should operate only in terms of view names that have been injected into it.
一个特别的视图名前缀能完成这个解耦:redirect:
。如果返回的视图名中含有redirect:
前缀,那么UrlBasedViewResolver
(及它的所有子类)就会接受到这个信号,意识到这里需要发生重定向。然后视图名剩下的部分会被解析成重定向URL。
这种方式与通过控制器返回一个重定向视图RedirectView
所达到的效果是一样的,不过这样一来控制器就可以只专注于处理并返回逻辑视图名了。如果逻辑视图名是这样的形式:redirect:/myapp/some/resource
,他们重定向路径将以Servlet上下文作为相对路径进行查找,而逻辑视图名如果是这样的形式:redirect:http://myhost.com/some/arbitrary/path
,那么重定向URL使用的就是绝对路径。
注意的是,如果控制器方法注解了@ResponseStatus
,那么注解设置的状态码值会覆盖RedirectView
设置的响应状态码值。
对于最终会被UrlBasedViewResolver
或其子类解析的视图名,你可以使用一个特殊的前缀:forward:
。这会导致一个InternalResourceView
视图对象的创建(它最终会调用RequestDispatcher.forward()
方法),后者会认为视图名剩下的部分是一个URL。因此,这个前缀在使用InternalResourceViewResolver
和InternalResourceView
时并没有特别的作用(比如对于JSP来说)。但当你主要使用的是其他的视图技术,而又想要强制把一个资源转发给Servlet/JSP引擎进行处理时,这个前缀可能就很有用(或者,你也可能同时串联多个视图解析器)。
与redirect:
前缀一样,如果控制器中的视图名使用了forward:
前缀,控制器本身并不会发觉任何异常,它关注的仍然只是如何处理响应的问题。