5.Resolving views

解析视图

简介

所有用于Web应用程序的MVC框架都提供了一种处理视图的方法。Spring 提供了视图解析器(View Resolver),使您可以在浏览器中渲染模型,而无需绑定到特定的视图技术。Spring 内置支持使用 JSP、Velocity 模板和 XSLT 视图等。例如,有关如何集成和使用多种不同的视图技术,请参阅第23章“视图技术”。

在 Spring 处理视图的方式中,两个重要的接口是 ViewResolverView

  • ViewResolver:提供视图名称与实际视图之间的映射。
  • View:处理请求的准备工作,并将请求交给某种视图技术。

Resolving views with the ViewResolver interface

使用 ViewResolver 接口解析视图

正如在“实现控制器”一节中讨论的那样,Spring Web MVC 控制器中的所有处理方法都必须解析为逻辑视图名称,可以是显式的(例如,返回一个字符串、视图或 ModelAndView)或隐式的(基于约定)。在 Spring 中,视图通过逻辑视图名称来处理,并由视图解析器解析。Spring 提供了相当多的视图解析器。下表列出了大部分视图解析器,并附带了一些示例。

表 22.3 视图解析器

ViewResolver描述
AbstractCachingViewResolver抽象视图解析器,缓存视图。通常在视图使用前需要准备,扩展此视图解析器可提供缓存功能。
XmlViewResolver接受一个用 XML 编写的配置文件的 ViewResolver 实现,配置文件的 DTD 与 Spring 的 XML bean 工厂相同。默认配置文件为 /WEB-INF/views.xml。
ResourceBundleViewResolver使用 ResourceBundle 中的 bean 定义的 ViewResolver 实现,通过基础名指定的 bundle 通常在类路径下定义。默认文件名为 views.properties。
UrlBasedViewResolver简单实现的 ViewResolver 接口,将逻辑视图名称直接解析为 URL,无需显式的映射定义。适用于逻辑名称与视图资源名称简单对应的情况。
InternalResourceViewResolverUrlBasedViewResolver 的便捷子类,支持 InternalResourceView(实际上是 Servlets 和 JSP)及其子类如 JstlView 和 TilesView。
VelocityViewResolver / FreeMarkerViewResolverUrlBasedViewResolver 的便捷子类,分别支持 VelocityView(实际上是 Velocity 模板)或 FreeMarkerView 及其自定义子类。
ContentNegotiatingViewResolver根据请求文件名或 Accept 头解析视图的 ViewResolver 实现。参见第22.5.4节“ContentNegotiatingViewResolver”。

示例

以 JSP 作为视图技术为例,可以使用 UrlBasedViewResolver。此视图解析器将视图名称翻译为 URL,并将请求交给 RequestDispatcher 来渲染视图。

<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

组合不同的视图技术

在 Web 应用程序中组合不同的视图技术时,可以使用 ResourceBundleViewResolver

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

ResourceBundleViewResolver 会检查由 basename 标识的 ResourceBundle,对于每个需要解析的视图,它使用属性 [viewname].(class) 的值作为视图类,并使用属性 [viewname].url 的值作为视图 URL。在下一章覆盖视图技术时可以找到示例。可以指定一个父视图,所有在属性文件中的视图都“继承”该父视图。这样可以指定一个默认的视图类,例如。

注意

AbstractCachingViewResolver 的子类会缓存它们解析的视图实例。缓存可以提高某些视图技术的性能。可以通过将 cache 属性设置为 false 来关闭缓存。此外,如果必须在运行时刷新某个视图(例如,当 Velocity 模板被修改时),可以使用 removeFromCache(String viewName, Locale loc) 方法。

Chaining ViewResolvers

链接 ViewResolvers

Spring 支持多个视图解析器。因此,您可以链接解析器,并在某些情况下覆盖特定视图。通过向应用程序上下文添加多个解析器,并在必要时设置 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>

<!-- 在 views.xml 中 -->

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

如果特定的视图解析器未能解析视图,Spring 会检查上下文中的其他视图解析器。如果存在其他视图解析器,Spring 将继续检查它们,直到解析出视图。如果没有视图解析器返回视图,Spring 将抛出 ServletException

视图解析器的契约规定,视图解析器可以返回 null 以表示未找到视图。然而,并非所有视图解析器都会这样做,因为在某些情况下,解析器无法检测视图是否存在。例如,InternalResourceViewResolver 内部使用 RequestDispatcher,调度是确定 JSP 是否存在的唯一方法,但此操作只能执行一次。VelocityViewResolver 和其他一些解析器也是如此。查看特定视图解析器的 javadoc 以了解其是否报告不存在的视图。因此,将 InternalResourceViewResolver 放在链中非最后位置会导致链未被完全检查,因为 InternalResourceViewResolver 总是返回视图!

Redirecting to Views

重定向到视图

如前所述,控制器通常返回一个逻辑视图名称,该视图名称通过视图解析器解析为特定的视图技术。对于通过 Servlet 或 JSP 引擎处理的视图技术(例如 JSP),这种解析通常通过 InternalResourceViewResolver 和 InternalResourceView 的组合来处理,这两者通过 Servlet API 的 RequestDispatcher.forward(…) 方法或 RequestDispatcher.include() 方法发出内部转发或包含。对于其他视图技术,例如 Velocity、XSLT 等,视图本身会将内容直接写入响应流。

有时希望在视图呈现之前向客户端发出 HTTP 重定向。例如,当一个控制器被 POST 数据调用时,响应实际上是委托给另一个控制器(例如,在表单提交成功时)。在这种情况下,普通的内部转发会导致另一个控制器也看到相同的 POST 数据,这在某些情况下可能会混淆预期的数据。另一种在显示结果之前执行重定向的原因是消除用户多次提交表单数据的可能性。在这种情况下,浏览器首先发送初始 POST 请求,然后收到重定向到不同 URL 的响应,最后浏览器对重定向响应中指定的 URL 执行后续的 GET 请求。因此,从浏览器的角度来看,当前页面反映的是 GET 的结果,而不是 POST 的结果。最终效果是用户无法通过刷新意外地重新提交相同的数据,刷新会强制执行结果页面的 GET,而不是重新发送初始的 POST 数据。

RedirectView

控制器响应的结果强制重定向的一种方法是让控制器创建并返回 Spring 的 RedirectView 实例。在这种情况下,DispatcherServlet 不会使用正常的视图解析机制。因为它已经得到了(重定向)视图,DispatcherServlet 只需指示视图执行其工作。RedirectView 随后调用 HttpServletResponse.sendRedirect() 向客户端浏览器发送 HTTP 重定向。

如果使用 RedirectView 并且视图由控制器本身创建,建议将重定向 URL 配置为注入控制器,以便它不被硬编码到控制器中,而是与视图名称一起在上下文中配置。 redirect: 前缀部分有助于实现这种解耦。

传递数据到重定向目标

默认情况下,所有模型属性都被认为是 URI 模板变量暴露在重定向 URL 中。剩余的属性中,那些原始类型或原始类型的集合/数组会自动作为查询参数附加。

如果模型实例是专门为重定向准备的,则将原始类型属性附加为查询参数可能是预期结果。然而,在注解控制器中,模型可能包含其他为渲染目的而添加的属性(例如下拉字段值)。为了避免这些属性出现在 URL 中,@RequestMapping 方法可以声明一个 RedirectAttributes 类型的参数,并使用它来指定要在 RedirectView 中提供的确切属性。如果方法确实重定向,则使用 RedirectAttributes 的内容。否则使用模型的内容。

RequestMappingHandlerAdapter 提供了一个名为 ignoreDefaultModelOnRedirect 的标志,用于指示如果控制器方法重定向,则不应使用默认模型的内容。相反,控制器方法应声明 RedirectAttributes 类型的属性,或者如果不这样做,则不应将任何属性传递给 RedirectView。MVC 命名空间和 MVC Java 配置将此标志设置为 false 以保持向后兼容性。然而,对于新应用程序,建议将其设置为 true。

请注意,在扩展重定向 URL 时,当前请求中的 URI 模板变量会自动可用,无需通过 Model 或 RedirectAttributes 明确添加。例如:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

将数据传递到重定向目标的另一种方法是通过 Flash Attributes。与其他重定向属性不同,Flash Attributes 保存在 HTTP 会话中(因此不会出现在 URL 中)。有关更多信息,请参见第 22.6 节“使用 Flash Attributes”。

redirect: 前缀

虽然使用 RedirectView 可以正常工作,但如果控制器本身创建了 RedirectView,那么无法避免控制器知道发生了重定向。这确实是不理想的,并且耦合过于紧密。控制器不应该关心响应如何处理。通常,它只应处理注入到它的视图名称。

特殊的 redirect: 前缀可以实现这一点。如果返回的视图名称带有前缀 redirect:,UrlBasedViewResolver(及其所有子类)会将其识别为需要重定向的特殊指示。视图名称的其余部分将被视为重定向 URL。

最终效果与控制器返回 RedirectView 相同,但现在控制器本身可以简单地操作逻辑视图名称。逻辑视图名称如 redirect:/myapp/some/resource 将相对于当前 Servlet 上下文进行重定向,而名称如 redirect:http://myhost.com/some/arbitrary/path 将重定向到绝对 URL。

请注意,如果控制器处理程序使用 @ResponseStatus 注解,则注解值优先于 RedirectView 设置的响应状态。

forward: 前缀

还可以使用 forward: 前缀用于最终由 UrlBasedViewResolver 和子类解析的视图名称。这会围绕视图名称的其余部分创建一个 InternalResourceView(最终执行 RequestDispatcher.forward()),该视图名称被视为 URL。因此,此前缀对于 InternalResourceViewResolver 和 InternalResourceView(例如 JSP)来说没有用。但是,当您主要使用另一种视图技术时,此前缀可能很有帮助,但仍希望强制转发由 Servlet/JSP 引擎处理的资源。(请注意,您也可以链接多个视图解析器。)

redirect: 前缀一样,如果将带有 forward: 前缀的视图名称注入控制器,则控制器不会检测到响应处理中发生的任何特殊情况。

ContentNegotiatingViewResolver

内容协商视图解析器

ContentNegotiatingViewResolver 并不直接解析视图,而是委托给其他视图解析器,选择与客户端请求的表示形式相似的视图。客户端请求服务器表示形式有两种策略:

  1. 使用不同的 URI 来请求不同的资源表示形式:通常通过在 URI 中使用不同的文件扩展名来实现。例如,URI http://www.example.com/users/fred.pdf 请求用户 fred 的 PDF 表示形式,而 http://www.example.com/users/fred.xml 请求 XML 表示形式。
  2. 使用相同的 URI 来定位资源,但设置 Accept HTTP 请求头来列出客户端理解的媒体类型:例如,HTTP 请求 http://www.example.com/users/fred,Accept 头设置为 application/pdf 请求用户 fred 的 PDF 表示形式,而 Accept 头设置为 text/xml 请求 XML 表示形式。这种策略称为内容协商。

注意:Accept 头在网页浏览器中无法设置。例如,在 Firefox 中,它固定为:Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8。因此,在开发基于浏览器的 Web 应用程序时,通常会看到使用不同 URI 来请求不同的表示形式。

为了支持资源的多种表示形式,Spring 提供了 ContentNegotiatingViewResolver,根据 HTTP 请求的文件扩展名或 Accept 头来解析视图。ContentNegotiatingViewResolver 本身并不执行视图解析,而是委托给通过 bean 属性 ViewResolvers 指定的视图解析器列表。

ContentNegotiatingViewResolver 通过将请求媒体类型与与其 ViewResolvers 相关联的 View 支持的媒体类型(也称为 Content-Type)进行比较来选择适当的 View 来处理请求。列表中的第一个具有兼容 Content-Type 的 View 将返回给客户端表示形式。如果 ViewResolver 链无法提供兼容的视图,则将查阅通过 DefaultViews 属性指定的视图列表。后一种选择适用于可以无论逻辑视图名称如何都能呈现当前资源适当表示形式的单例视图。Accept 头可以包括通配符,例如 text/*,在这种情况下,Content-Type 为 text/xml 的视图是兼容匹配。

要支持基于文件扩展名的自定义视图解析,请使用 ContentNegotiationManager。参见第 22.16.6 节“内容协商”。

以下是 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"/>

InternalResourceViewResolver 处理视图名称和 JSP 页面的翻译,而 BeanNameViewResolver 根据 bean 的名称返回视图。(有关 Spring 如何查找和实例化视图的更多详细信息,请参见“使用 ViewResolver 接口解析视图”部分。)在此示例中,content bean 是继承自 AbstractAtomFeedView 的类,该类返回 Atom RSS feed。有关创建 Atom Feed 表示形式的更多信息,请参见 Atom 视图部分。

在上述配置中,如果请求带有 .html 扩展名,视图解析器会查找与 text/html 媒体类型匹配的视图。InternalResourceViewResolver 提供与 text/html 匹配的视图。如果请求带有 .atom 文件扩展名,视图解析器会查找与 application/atom+xml 媒体类型匹配的视图。如果视图名称返回为 content,则由 BeanNameViewResolver 提供与 SampleContentAtomView 匹配的视图。如果请求带有 .json 文件扩展名,DefaultViews 列表中的 MappingJackson2JsonView 实例将被选中,而不考虑视图名称。或者,客户端请求可以在不带文件扩展名的情况下进行,但 Accept 头设置为首选媒体类型,同样的请求到视图的解析也会发生。

注意:如果 ContentNegotiatingViewResolver 的 ViewResolvers 列表未显式配置,它会自动使用应用程序上下文中定义的任何 ViewResolvers。

下列控制器代码返回 URI 形式为 http://localhost/content.atom 或 Accept 头为 application/atom+xmlhttp://localhost/content 的 Atom RSS feed:

@Controller
public class ContentController {

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

    @GetMapping("/content")
    public ModelAndView getContent() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("content");
        mav.addObject("sampleContentList", contentList);
        return mav;
    }

}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值