关闭

SpringMVC之ContentNegotiatingViewResolver详解

标签: spring mvc
4476人阅读 评论(0) 收藏 举报
分类:

诞生自 3.0 的 ContentNegotiatingViewResolver 从字义不太容易知道在做什么或怎么用,那就从 Source code 好好来研究一下。

每个 ViewResolver 的重点就是 resolveViewName()。

public View resolveViewName(String viewName, Locale locale) throws Exception {
  RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
  Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
  List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
  if (requestedMediaTypes != null) {
    List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
    View bestView = getBestView(candidateViews, requestedMediaTypes);
    if (bestView != null) {
      return bestView;
    }
  }
  if (this.useNotAcceptableStatusCode) {
    // ...
    return NOT_ACCEPTABLE_VIEW;
  }
  else {
    logger.debug("No acceptable view found; returning null");
    return null;
  }
}


 

得到三个线索,分别为
getMediaTypes()、getCandidateViews() 与 getBestView()


 除此之后,可以知道如果找不到合适的 View 对象,有两条路可以走,如果将 useNotAcceptableStatusCode  设为 true(预设为 false),那就会丢出 406 Not Acceptable 错误,如果设为 false,那就回传 null,表示交由下一个 View Resolver 处理,这就是 View Resolver Chaining。

getBestView()

getBestView() 是 private 而且没有加批注,所以在 JavaDoc 里看不到。

private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes) {
  for (View candidateView : candidateViews) {
    if (candidateView instanceof SmartView) {
      SmartView smartView = (SmartView) candidateView;
      if (smartView.isRedirectView()) {
        // ...
        return candidateView;
      }
    }
  }
  for (MediaType mediaType : requestedMediaTypes) {
    for (View candidateView : candidateViews) {
      if (StringUtils.hasText(candidateView.getContentType())) {
        MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
        if (mediaType.includes(candidateContentType)) {
          // ...
          return candidateView;
        }
      }
    }
  }
  return null;
}


 

内容分成两部份,第一部份在判断是否为 RedirectView(SmartView 唯一的实作),是的话就直接送走,不用往下走了,第二部份才是重点。

ContentNegotiatingViewResolver 从字义直翻是「内容谈判」,不懂,那叫做「内容协调」,还是不懂,那叫做「依据 request 决定 response」,嗯,废话,,哪个 request 不是这样!

不完全是,看例子。

http://.../hello.html

http://.../hello.pdf


http://.../hello.xml


 

看似三个 url,需要三个 requestMapping 处理,但其实产出给 View 的内容是一样的,那可不可以用同一个 requestMapping 处理,然后依据「某些讯息」回传不同格式的 View 对象呢?这就是 ContentNegotiatingViewResolver 要做的事情。

ContentNegotiatingViewResolver 右手拿着 request 想要的格式,左手握着可能的 View 对象(怎么来的?稍后讲),交叉比对,若有符合就回传,举例来说,request 想要 PDF,就去 View 里找有没有提供 PDF 格式的 View 对象,若有则回传,若没有呢?再看 request 里有没有第二志愿、第三志愿,如果都没有,那就爆了,这就是 getBestView() 第二部份在做的事情。

从 getBestView() 往前追踪,传进来的两个参数分别为 List<View> candidateViews 与 List<MediaType> requestedMediaTypes,先看第二个参数,也就是 ContentNegotiatingViewResolver 右手拿的东西,也是前面提到的「
依据『某些讯息』回传不同格式的 View 对象」的讯息来源。

这就可以追踪到 getMediaType() 这个 method。

getMediaType()

protected List<MediaType> getMediaTypes(HttpServletRequest request) {
  if (this.favorPathExtension) {
    String requestUri = urlPathHelper.getLookupPathForRequest(request);
    String filename = WebUtils.extractFullFilenameFromUrlPath(requestUri);
    MediaType mediaType = getMediaTypeFromFilename(filename);
    if (mediaType != null) {
      // ...
      return Collections.singletonList(mediaType);
    }
  }
  if (this.favorParameter) {
    if (request.getParameter(this.parameterName) != null) {
      String parameterValue = request.getParameter(this.parameterName);
      MediaType mediaType = getMediaTypeFromParameter(parameterValue);
      if (mediaType != null) {
        // ...
        return Collections.singletonList(mediaType);
      }
    }
  }
  if (!this.ignoreAcceptHeader) {
    String acceptHeader = request.getHeader(ACCEPT_HEADER);
    if (StringUtils.hasText(acceptHeader)) {
      // ...
    }
  }
  if (this.defaultContentType != null) {
    // ...
    return Collections.singletonList(this.defaultContentType);
  }
  else {
    return Collections.emptyList();
  }
}


 

getMediaType()  有四种选项,依序为扩展名、request 参数、Accept Header 与默认值。

扩展名

预设开启(favorPathExtension),也是建议使用的方式,因为扩展名可以让浏览器知道回传的文件类型,而使用正确的应用程序来开启。

http://.../hello.html

http://.../hello.pdf


http://.../hello.xml


 


request 参数

预设关闭(favorParameter),需搭配 parameterName(预设为 format)使用。

http://.../hello?format=html

http://.../hello?format=pdf


http://.../hello?format=xml


 

这边有个奇怪的地方,ContentNegotiatingViewResolver 有个 mediaTypes 属性,是用来设定 ContentNegotiatingViewResolver 需要处理的扩展名,奇怪的地方在于前一项「扩展名」不需要 mediaTypes,而这一项「request 参数」一定需要。

protected MediaType getMediaTypeFromFilename(String filename) {
  String extension = StringUtils.getFilenameExtension(filename);
  if (!StringUtils.hasText(extension)) {
    return null;
  }
  extension = extension.toLowerCase(Locale.ENGLISH);
  MediaType mediaType = this.mediaTypes.get(extension);
  if (mediaType == null) {
    String mimeType = getServletContext().getMimeType(filename);
    if (StringUtils.hasText(mimeType)) {
      mediaType = MediaType.parseMediaType(mimeType);
    }
    // ...
    if (mediaType != null) {
      this.mediaTypes.putIfAbsent(extension, mediaType);
    }
  }
  return mediaType;
}


 

从扩展名取 MediaType 时,如果不存在 mediaTypes 属性定义范围内,则会去 ServletContext 里找,这边一般的档案都应该找得到,找到之后,甚至回存到 mediaTypes 里。

protected MediaType getMediaTypeFromParameter(String parameterValue) {
  return this.mediaTypes.get(parameterValue.toLowerCase(Locale.ENGLISH));
}


 

但是从 request 参数里找 MediaType 时,则完全不管找不到的状况。

换句话说,也就是说,
使用 request 参数指定扩展名时,一定要将这个扩展名也定义在 mediaTypes 属性里

唯一我能想到的合理解释就是,使用 request 参数不一定要用「真实的扩展名」,也许可以用暗号,然后在 mediaTypes 里再加上暗号与真实 MediaType 的对照。

Accept Header

预设开启(ignoreAcceptHeader),但无法从网址中指定,所以常用在由程序送出 request 这类的 Web service。

默认值

由 defaultContentType 设定。


getCandidateViews()

看完 ContentNegotiatingViewResolver  右手的 MediaType,来看看左手的 View 对象,View 对象有两个来源,分别为其他的 ViewResolver 与默认值。

其他的 ViewResolver?神奇吧,ContentNegotiatingViewResolver 除了默认的 View 对象,也就是由 defaultViews 定义的 View 对象以外,本身不事生产,而是向其他的 ViewResolver 要 View 对象。

ContentNegotiatingViewResolver 预设是由 Spring 找出所有其他的 ViewResolver 来建立 View 对象,但是也可以透过 viewResolvers 属性明确指定使用哪些 ViewResolver。

也因为这个特性,ContentNegotiatingViewResolver 的 order 属性必须有相当高的优先权,至少得在被用到的 ViewResolver 之前,不然就发挥不了作用了,默认值为 Ordered.HIGHEST_PRECEDENCE,也就是最小值 Integer.MIN_VALUE。

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
    throws Exception {

  List<View> candidateViews = new ArrayList<View>();
  for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
      candidateViews.add(view);
    }
    for (MediaType requestedMediaType : requestedMediaTypes) {
      List<String> extensions = getExtensionsForMediaType(requestedMediaType);
      for (String extension : extensions) {
        String viewNameWithExtension = viewName + "." + extension;
        view = viewResolver.resolveViewName(viewNameWithExtension, locale);
        if (view != null) {
          candidateViews.add(view);
        }
      }

    }
  }
  if (!CollectionUtils.isEmpty(this.defaultViews)) {
    candidateViews.addAll(this.defaultViews);
  }
  return candidateViews;
}


 

        先要到所有的 ViewResolver,当然本身除外,然后用 viewName 去找,找到后再用 MediaType 去过滤,都符合的就取得 View 对象候选资格,全部的 ViewResolver 看过之后,再加入由 defaultViews 属性定义的 View 对象(这属性好用吗?),就是左手的 View 对象。

1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

SpringBoot学习笔记(四) SpringBoot Web相关的自动配置

SpringBoot Web相关的自动配置
  • zxzzxzzxz123
  • zxzzxzzxz123
  • 2017-04-19 11:35
  • 5000

spring3和spring4的一些需要注意的地方

最近搭建了一个框架,开始用的spring3.1.1 后来升级到spring4.2.0,把遇到的问题记录一下当做备份了 1、java.lang.NoSuchMethodError: org.s...
  • cr135810
  • cr135810
  • 2016-08-24 17:41
  • 16328

Spring 4 MVC 视图解析器(XML JSON PDF等) 纯注解(带源码)【推荐】

原文地址:http://websystique.com/springmvc/spring-4-mvc-contentnegotiatingviewresolver-example/ 【本系列其他教程...
  • w605283073
  • w605283073
  • 2016-05-07 16:37
  • 4608

SpringMVC之ContentNegotiatingViewResolver解析器之实例分析

RESTful服务中很重要的一个特性即是同一资源,多种表述我们使用ContentNegotiatingViewResolver就可以做到,这个视图解析器允许你用同样的内容数据来呈现不同的view。 ...
  • zmx729618
  • zmx729618
  • 2016-05-31 17:44
  • 935

SpringMVC ContentNegotiatingViewResolver -- 根据请求资源名选择视图

利用使用ContentNegotiatingViewResolver,这个视图解析器允许你用同样的内容数据来呈现不同的view。
  • fw0124
  • fw0124
  • 2015-09-09 14:48
  • 1159

SpringMVC大坑一枚:ContentNegotiatingViewResolver可能不利于SEO

总之一句话,公司项目boss采用这种配置,最大的目的就是,很方便地响应.html页面和.json数据,甚至是2者同时支持。 但是,实际情况证实,这种情况很可能 不利于百度-360等搜索引擎的优化。 ...
  • FansUnion
  • FansUnion
  • 2014-12-11 22:12
  • 1761

SpringMVC之ContentNegotiatingViewResolver实现根据请求资源名选择视图

使用ContentNegotiatingViewResolver,这个视图解析器允许你用同样的内容数据来呈现不同的view。 它支持如下面描述的三种方式: 1)使用扩展名 http://loca...
  • zmx729618
  • zmx729618
  • 2017-02-27 14:31
  • 267

Springmvc同一资源不同描述配置-contentnegotiatingviewresolver

定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 --> 前面 -->
  • VagueCoder
  • VagueCoder
  • 2015-07-26 08:48
  • 2479

ContentNegotiatingViewResolver - 内容协商视图解析器

RESTful服务中很重要的一个特性即是同一资源,多种表述我们使用ContentNegotiatingViewResolver就可以做到,这个视图解析器允许你用同样的内容数据来呈现不同的view. 如...
  • afandaafandaafanda
  • afandaafandaafanda
  • 2015-09-25 10:24
  • 895

Spring mvc ContentNegotiatingViewResolver 根据路径后缀,选择不同视图

Spring mvc通过ContentNegotiatingViewResolver 实现根据路径后缀,选择不同视图
  • LittleSkey
  • LittleSkey
  • 2016-11-29 15:21
  • 526
    个人资料
    • 访问:1507573次
    • 积分:17237
    • 等级:
    • 排名:第667名
    • 原创:155篇
    • 转载:913篇
    • 译文:34篇
    • 评论:212条
    文章分类
    最新评论