9、Spring MVC 之 Resolving views

所有的MVC框架都会为web应用提供一个方法去访问view。Spring提供了页面解析,这个能够使你不需要使用特定的view技术在浏览器中渲染你的model.Spring默认提供了让你能够使用JSP,Velocity模板和XSLT页面技术.
在Spring中ViewResolver和View这两个接口非常重要,他们是用来处理页面的.ViewResolver提供了一个页面名称与真实页面的映射。View接口预处理request,并把request传递给其中一个view技术.

1、ViewResolver – 解析页面

在之前的blog – Spring MVC 实现Controller中,在Spring Web MVC的Controller中的所有handler方法必须解析到一个合理的,或者明确的(e.g. 返回一个String,View或者ModelAndView)或者含蓄地(i.e.,基于conventions)或者合理的view名称.Spring MVC中的页面名称是通过view解析器来解析的。Spring有不少页面解析器.下面的table列举了大多数的解析器,后面有相就的例子:

ViewResolverDescription
AbstractCachingViewResolver缓存视图的抽象视图解析器。通常页面在使用之前需要缓存,可以继承这个页面解析器来提供缓存
XmlViewResolver实现ViewResolver接受用XML和DTD写的配置文件作为spring的XML的bean工厂。默认的配置文件是/WEB-INF/views.xml.
ResourceBundleViewResolver实现ViewResolver使用ResourceBundle定义的Bean,指定的绑定包的名称.你可以通过在classpath位置properties的文件绑定。默认的配置文件名称为views.properties
UrlBasedViewResolverViewResolver接口的简单实现,如果没有明显的映射定义就直接解析逻辑视图名称到url.如果你的逻辑名称以直接的方式匹配你视图资源的名字,不需要任何的映射。
InternalResourceViewResolverUrlBasedViewResolver很有用的子类,支持InternalResourceView(对Servlets与JSPs生效),JstlView和TilesView的子类。你可以通过这个解析器的setViewClass(..)方法给所有的页面指定页面类。更多细节可以看UrlBasedViewResolver的 javadocs
VelocityViewResolver / FreeMarkerViewResolverUrlBasedViewResolverr的子类,支持VelocityView(对velocity模板生效)或者FreeMarkerView,分别是它们的自定义子类
ContentNegotiatingViewResolverViewResolver接口的实现,用于解析基于请求的视图文件的名字或者Header中的Accept

以JSP页面技术作为例子,你可以使用UrlBasedViewResolver。这个页面解析器翻译一个页面名称为一个URL.通过RequestDispatcher来处理request然后渲染页面。

<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作为逻辑页面名称的时候,页面解析器会转发request到RequestDispatcher然后会发送请求到/WEB-INF/jsp/test.jsp.
当你在web应用中想使用不同的的页面解析技术的时候,你可以使用ResourceBundleViewResolver。

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver通过base name检查ResourceBundle.对于每一个页面它都支持解析。它使用properties文件中[viewname].(class)的值作为view class。并且使用properties文件中[viewname].url作为view url。可以在下一个章节中找到例子。正如你看到的,你可以定义一个父页面,所有在properites文件中都可以继承。通过这种方式你可以指定一个默认的view class。

注意:AbstractCachingViewResolver 的子类能够缓存它们解析页面的实例。缓缓能够提高某些页面技术的性能。可以通过设置cache 的值为false来关闭。此外,如果你必须在运行时刷新某个页面(例如当一个velocity模板被改变)。你可以使用removeFromCache(String viewName, Locale loc)方法。

2、Chaining ViewResolvers

spring支持多个页面解析器。因此在某些情况下,针对特殊的view你可以使用链式解析器。在你的应用上下文中添加多个解析器用来链式解析页面。如果需要的话,你还可以通过order设置特定的顺序.这个Order的值越大,就会在这个链的最尾端。
在下面的例子中,页面解析器链包含2个解析器。一个是InternalResourceViewResolver通常自动做为解析器链的最后一个。另一个是XmlViewResolver用来指定Excel页面。Excel页面是不被InternalResourceViewResolver支持的。

<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会继续检查他们直到这个页面被解析。如果页面解析器没有返回一个view,Spring会抛出ServletException.

一个视图解析器可以指定返回null,这叫表明找不到这个view.不是所有的页面解析器都会这样做,因为在一些情况下,解析器检测这个页面是否存在.如果把InternalResourceViewResolver不是放在最后一个,那么它之外的解析器就无法被检查,因为InternalResourceViewResolver总是会返回一个view.

3、Redirecting to Views

在之前我们提到,Controller通常返回一个合逻辑的view名称。一个视图解析器解析为一个特定的视图技术.JSPd页面技术通过Servlet或者JSP引擎处理,基本上是通过InternalResourceViewResolverInternalResourceView的组合使用Servlet的API方法RequestDispatcher.forward(..)或者RequestDispatcher.include()进行内部转发。其它的页面技术,像Velocity,XSLT等等。视图本身写的内容直接到响应流。

在页面被渲染之前,有时需要发送一个HTTP重定向回客户端。例如当一个Controller被POST数据请求,并且response实际上已经被委托到别外一个Controller(例如一个表单提交)。对于这种情况,一个正常的内部转发意味着另外的Controller将会也可看到同样的POST的数据。这样可能存在问题,它可以与其他预期的数据混淆。另外一个原因是在展示结果之前进行重定向这样会消除用户多次提交表单数据。在这种场景下,浏览器会先发送一个初始的POST,它将会收到一个跳转到不同URL的response,最后浏览器会执行返回的跳转reponse中的GET类型的URL。因此对于浏览器,当前页面并不能反映POST请求而是一个GET请求。这个结果的影响就是用户不可能通过刷新来执行重复的发送POST请求。刷新会得到那个GET结果的页面,而不是再次发送最初始的POST数据请求。

1)RedirectView

在Controller中可以通过返回Spring中RedirectView实例来进行重定向。在这种情况下,DispatcherServlet不会使用一般的页面解析机制。因为它已经有重定向的页面了,DispatcherServlet仅仅需要命令页面去做这个工作。RedirectView会调用HttpServletResponse.sendRedirect()发送一个HTTP重定向请求给客户端浏览器。

如果你使用RedirectView并且这个页面是被Controller自身创建的,推荐你配置跳转URL通过注入的方式到Controller中,这样可以有助于去耦。具体可以看下面的章节“The redirect: prefix”.

2)Passing Data To the Redirect Target

所有的model属性默认被暴露给重定向URL中的URI模板变量。其它剩下的属性,原始类型或者collections/arrays类型自动附加为查询参数。

如果model实例是特定准备给重定向的,那么附加的原始类型的参数作为查询参数可能渴望获取到结果。然而,带注解的controller中model中可能包含附加的属性用于渲染目的(e.g. 下拉字段的值)。为了有这些属性附加在URL中,@RequestMapping可以声明一个参数类型为RedirectAttributes并且使用它使得RedirectView可以获取指定的属性。如果方法进行重定向,RedirectAttributes的content将会被使用。另外model的content也会被使用。

RequestMappingHandlerAdapter对象提供了一个标记位叫做"ignoreDefaultModelOnRedirect"。使用这个属性可以表明如果这个是Controller方法进行redirect,那么默认Model的content将会不被使用。这个Controller的方法将会被声明为RedirectAttributes类型的属性或者没有attributes将会被传递到RedirectView。不管你使用MVC命名空间还是MVC的Java式编程都可以设置这个标记位为false保证它能够向后兼容。但是,如果是新应用的话,推荐你设置它为true.

注意:来自当前request的URI模板继承自跳转URL的变量将会被自动的获取到。并且不需要通过Model或者RedirectAttributes明确的添加。For example:

@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

传递数据到redirect目标的另一个方法是通过Flash Attributes。与其它redirect属性,flash属性被保存到HTTP session不同.具体细节可以可以看下一篇Blog – “Using flash attributes”

3)redirect:前缀

当使用RedirectView工作的时候,Controller会自己创建RedirectView对象。这样就不可避免Controller会意识到将会发生重定向。这样就会不是最好的并且太藕合了。Controller事实上不应该关心response将会如何处理。一般来说应该通过被注入视图名称来进行运转。

这个特殊的redirect:前缀允许你实现它。如果一个页面名称返回的时候带有redirect:前缀的话。UrlBasedViewResolver(与它所有的子类)将会认为这个特殊的指示表示需要重定向。rest的页面名称将会被做为跳转URL进行对待。

这样就可以达到Controller返回一个RedirectView对象相同的效果。但是现在Controller本身可以简单操作的逻辑视图名称。一个逻辑视图名称就好像redirect:/myapp/some/resource将会重定向到当前Servlet context相对地址。视力名称是redirect:http://myhost.com/some/arbitrary/path的时候将会重定向到一个绝对地址的URL中。

4)forward:前缀

页面名称同样可以使用特殊的forward:前缀,它最后也是会被UrlBasedViewResolver与它的子类解析。这个rest页面名称会创建一个InternalResourceView(最后调用RequestDispatcher.forward())。因此这个前缀对于InternalResourceViewResolverInternalResourceView无效(例如JSPs).当你主要使用另外的页面技术的话,如果你还想要通过Servlet/JSP引擎处理资源的转发,这个前缀就很有用了。(注意:你可能需要使用多个页面解析技术链)。

redirect:前缀一样,如果一个页面名称前面添加forward:前缀被注入到Controller中。这个Controller不会发现发生了什么response的处理响应。

4、ContentNegotiatingViewResolver

ContentNegotiatingViewResolver选择客户端请求表述相似的页面,它自身并不解析页面,它会委托给其它的页面解析器。下面2个策略是请求服务端的的代表:
1. 对于每个资源使用有区分的URI,典型的是在URI中使用不同的文件扩展名。例如,URI是http://www.example.com/users/fred.pdf请求用户的fred.pdf,但http://www.example.com/users/fred.xml请求的是XML文件。
2. 客户端使用同样的URI定位资源,但是可以设置HTTP请求头中的Accept成与之对应的media类型。例如,一个HTTP请求是 http://www.example.com/users/fred并且带有请求头中Accept是 application/pdf表示请求users下面的fred.pdf文件。当使用http://www.example.com/users/fred带有请求头中Accepttext/xml表示请求users下面的fred.xml文件。

为了支持多种资源代表,Spring提供ContentNegotiatingViewResolver来解析页面请求基于文件扩展名或者在HTTP请求中带有Accept参数。ContentNegotiatingViewResolver自身不会执行页面解析,但是它会委托给你指定属性的viewResolvers的页面解析器。

ContentNegotiatingViewResolver会通过比较请求中的media类型(HTTP的Head的Content-Type)查找适合的View对象来处理请求。首先View对象能够兼容客户端请求的Content-Type。如果这个兼容的页面不被ViewResolver链提供,然后就会请求ContentNegotiatingViewResolver中的defaultViews属性。后面的选项最好是单个的Views这个就会能渲染一个表示合适的当前的资源不管逻辑视图名称。Accept参数可能包含通配符(e.g. text/*),这个如果一个页面的Content-Type是text/xml将会兼容。

如果想要支持自定义页面分辨的文件扩展名,可以使用ContentNegotiationManager

下面是ContentNegotiatingViewResolver配置的例子:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="viewResolvers">
        <list>
            <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/jsp/"/>
                <property name="suffix" value=".jsp"/>
            </bean>
        </list>
    </property>
    <property name="defaultViews">
        <list>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </list>
    </property>
</bean>

<bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>

BeanNameViewResolver返回一个基于bean名称的页面,InternalResourceViewResolver处理转化页面名称为JSP页面。在这个例子中content这个bean是继承自AbstractAtomFeedView,它会返回一个Atom RSS feed.

在上面的配置中,如果一个请求是.html扩展。这个页面解析会查找一个匹配media类型为text/html的页面。InternalResourceViewResolver会提供一个匹配text/html的页面。如果请求是.atom扩展名,那么页面解析器匹配media类型为application/atom+xml的页面。如果页面名称返回是content,那么BeanNameViewResolver会提供页面映射SampleContentAtomView。如果请求的文件扩展名是.json,不管页面名称是什么,DefaultViews中的MappingJackson2JsonView实例将会被选择。

对于URI是http://localhost/content.atom或者http://localhost/content带有Accept header为 application/atom+xml.下面是相应的Controller返回Atom RSS feed的代码。

@Controller
public class ContentController {

    private List<SampleContent> contentList = new ArrayList<SampleContent>();

    @RequestMapping(path="/content", method=RequestMethod.GET)
    public ModelAndView getContent() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("content");
        mav.addObject("sampleContentList", contentList);
        return mav;
    }

}

因为水平有限,翻译不足之处还望见谅。
原文地址:spring-framework-reference-4.2.6.RELEASE

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值