SpringMVC(9) - 解析视图

参考:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#mvc-viewresolver

 

所有MVC框架都为Web应用程序提供了一种处理视图的方法。 Spring提供了视图解析器,可以在浏览器中呈现模型,而无需与特定的视图技术联系起来。开箱即用,Spring允许使用JSP,Velocity模板和XSLT视图。

对Spring处理视图的方式很重要的两个接口是ViewResolver和View。 ViewResolver提供视图名称和实际视图之间的映射。 View接口解决了请求的准备问题,并将请求交给其中一种视图技术。

 

1. 使用ViewResolver接口解析视图
Spring Web MVC控制器中的所有处理器方法必须显式地(例如,通过返回String、View或ModelAndView)或隐式地(基于约定)来解析为逻辑视图名称。 Spring中的视图由逻辑视图名称处理,并由视图解析器解析。 Spring带有相当多的视图解析器。该表列出了其中大部分内容;下面是几个例子。

ViewResolverDescription

AbstractCachingViewResolver

缓存视图的抽象视图解析器。视图通常需要准备才能使用;扩展此视图解析器提供缓存。

XmlViewResolver

ViewResolver的实现,它接受XML中编写的配置文件,该配置文件使用与Spring的XML bean工厂相同的DTD。默认配置文件是/WEB-INF/views.xml。

ResourceBundleViewResolver

ViewResolver的实现,它使用由bundle base name指定的ResourceBundle中定义的bean。通常,在属性文件中定义捆绑包,该文件位于类路径中。默认文件名是views.properties。

UrlBasedViewResolver

ViewResolver接口的简单实现,它可以直接将逻辑视图名称解析为URL,而无需显式映射定义。如果逻辑名称与视图资源的名称直接匹配,则这是合适的,而不需要任意映射。

InternalResourceViewResolver

UrlBasedViewResolver的便捷子类,支持InternalResourceView(实际上是Servlet和JSP)和子类,如JstlView和TilesView。可以使用setViewClass(..)为此解析器生成的所有视图指定视图类。

VelocityViewResolver / FreeMarkerViewResolver

UrlBasedViewResolver的便捷子类,分别支持VelocityView(实际上是Velocity模板)或FreeMarkerView,以及它们的自定义子类。

ContentNegotiatingViewResolver

ViewResolver接口的实现,该接口根据请求文件名或Accept头解析视图。

例如,使用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,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的值作为视图网址。示例可以在下一章中找到,其中包括视图技术。可以标识父视图,属性文件中的所有视图都从该视图中“扩展”。这样,可以指定默认视图类。

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

 

2. 链接ViewResolver
Spring支持多个视图解析器。因此,可以链接解析器,例如,在某些情况下覆盖特定视图。可以通过向应用程序上下文添加多个解析程序来链接视图解析器,并在必要时通过设置order属性来指定排序。order属性越高,视图解析器在链中的位置越靠后。

在下面的示例中,视图解析器链包含两个解析器,一个InternalResourceViewResolver,它始终自动定位为链中的最后一个解析器,以及一个用于指定Excel视图的XmlViewResolver。 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和其他一些同样适用。查看特定视图解析器的javadoc,以查看它是否报告不存在的视图。因此,将一个InternalResourceViewResolver放在链中的最后一个位置会导致链未被完全检查,因为InternalResourceViewResolver将始终返回一个视图!

 

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

有时需要在呈现视图之前向客户端发出HTTP重定向。例如,当使用POST数据调用一个控制器时,这是可取的,并且响应实际上是对另一个控制器的委托(例如,在成功的表单提交上)。在这种情况下,正常的内部转发将意味着另一个控制器也将看到相同的POST数据,如果它可能将其与其他预期数据混淆,则可能存在问题。在显示结果之前执行重定向的另一个原因是消除用户多次提交表单数据的可能性。在这种情况下,浏览器将首先发送初始POST;然后它会收到重定向到不同URL的响应;最后,浏览器将对重定向响应中指定的URL执行后续GET。因此,从浏览器的角度来看,当前页面不反映POST的结果,而是反映GET的结果。最终结果是用户无法通过执行刷新重新发布相同数据。刷新强制结果页面的GET,而不是重新发送初始POST数据。

3.1 RedirectView
作为控制器响应的结果,强制重定向的一种方法是控制器创建并返回Spring的RedirectView实例。在这种情况下,DispatcherServlet不使用普通的视图解析机制。而是因为它已经被赋予(重定向)视图,DispatcherServlet只是指示视图执行其工作。 RedirectView依次调用HttpServletResponse.sendRedirect()将HTTP重定向发送到客户端浏览器。

如果使用RedirectView并且视图是由控制器本身创建的,则建议将重定向URL配置注入控制器,以便它不会被固定在控制器中,而是在上下文中与视图名称一起配置。

3.2 将数据传递给重定向目标
默认情况下,所有模型属性都被视为在重定向URL中公开为URI模板变量。在其余属性中,原始类型或基本类型的集合/数组将自动附加为查询参数。

如果专门为重定向准备了模型实例,则将原始类型属性作为查询参数附加可能是期望的结果。但是,在带注解的控制器中,模型可能包含为渲染目的而添加的附加属性(例如,下拉字段值)。为了避免在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属性。与其他重定向属性不同,Flash属性保存在HTTP会话中(因此不会出现在URL中)。

3.3 redirect:prefix
虽然RedirectView的使用工作正常,但如果控制器本身创建RedirectView,则无法避免控制器意识到重定向正在发生的事实。这实际上并不是最理想的,而且会把事情过于紧密。控制器不应该真正关心如何处理响应。通常,它应该仅根据已注入其中的视图名称进行操作。

特殊的redirect:前缀允许完成此操作。如果返回的视图名称具有前缀redirect:,则UrlBasedViewResolver(以及所有子类)会将此识别为需要重定向的特殊指示。视图名称的其余部分将被视为重定向URL。

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

请注意,控制器处理器使用@ResponseStatus进行注释,注解值优先于RedirectView设置的响应状态。

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

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

 

4. ContentNegotiatingViewResolver
ContentNegotiatingViewResolver本身不解析视图,而是委托给其他视图解析器,选择类似于客户端请求的表示的视图。客户端从服务器请求表示存在两种策略:

  • 通常通过在URI中使用不同的文件扩展名为每个资源使用不同的URI。例如,URI http://www.example.com/users/fred.pdf请求用户fred的PDF表示,并且http://www.example.com/users/fred.xml请求XML表示。
  • 使用相同的URI为客户端定位资源,但设置Accept HTTP请求头以列出它理解的媒体类型。例如,http://www.example.com/users/fred的HTTP请求,其Accept头设置为application/pdf,请求用户fred的PDF表示,而http://www.example.com/users/fred与Accept头设置为text/xml请求XML表示。此策略称为内容协商。

注:Accept头的一个问题是无法在HTML中的Web浏览器中设置它。例如,在Firefox中,它固定为:

Accept: text/html,application/xhtml+xmlapplication/xml;q=0.9,*/*;q=0.8
因此,在开发基于浏览器的Web应用程序时,通常会看到为每个表示使用不同的URI。

 

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

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

以下是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的名称返回视图。 在此示例中,content bean是继承自AbstractAtomFeedView的类,后者返回Atom RSS提要。

在上面的配置中,如果使用.html扩展名发出请求,则视图解析程序将查找与text/html媒体类型匹配的视图。 InternalResourceViewResolver为text/html提供匹配视图。如果请求是使用文件扩展名.atom进行的,则视图解析程序将查找与application/atom+xml媒体类型匹配的视图。此视图由BeanNameViewResolver提供,如果返回的视图名称是content,则映射到SampleContentAtomView。如果请求是使用文件扩展名.json进行的,则无论视图名称如何,都将选择DefaultViews列表中的MappingJackson2JsonView实例。或者,可以在没有文件扩展名的情况下进行客户端请求,但将Accept头设置了首选媒体类型,那么会对视图进行相同请求解析。

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

相应的控制器代码返回一个Atom RSS提要,该提要用于http://localhost/content.atom或http://localhost/content形式的URI以及Accept头为application/atom+xml,如下所示。

@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;
    }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值