Spring--视图内容协商(三)

Spring--视图内容协商(三)

本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解做的笔记。

上两篇文章,简单介绍了一下Spring的视图内容协商。接下来我们针对 REST 内容协商做一下介绍。

没想到截图效果怎么不好,实在是失落。

大纲

  • 理解REST请求媒体类型
  • REST内容协商流程
  • REST内容协商源码分析

理解REST请求媒体类型

理解解析请求的媒体类型

在我们使用Spring开发的时候,相信 @RequestMapping 这个注解再也熟悉不过了,相信使用Restful API 接口形式开发的小伙伴们都避免不了设置API的媒体类型 比如 AcceptContent-Type ,在前一篇我们也说过 Spring 通过 ContentNegotiationManagerContentNegotiationStrategy 解析请求中的媒体类型。

HeaderContentNegotiationStrategy 为例

  • 如果解析成功,则返回合法的MediaType集合。
  • 否则,返回MediaType.ALL默认的媒体类型,也就是 */*

在这里我们可以看一下HeaderContentNegotiationStrategy 源码,具体解释已经在注释中给出

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
      throws HttpMediaTypeNotAcceptableException {
   //获取 Accept的 媒体类型的字符串数组
   String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
   //如果为空 则返回 全部类型 也就是 */*
   if (headerValueArray == null) {
      return MEDIA_TYPE_ALL_LIST;
   }

   List<String> headerValues = Arrays.asList(headerValueArray);
   try {
      //转换为Spring 内置媒体类型集合 
      List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
       //最佳媒体类型排序
      MediaType.sortBySpecificityAndQuality(mediaTypes);
      return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotAcceptableException(
            "Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
   }
}

当然 在 ContentNegotiationManager 中也会有判断

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    //循环遍历协商处理策略
   for (ContentNegotiationStrategy strategy : this.strategies) {
      //获取媒体类型 比如 HeaderContentNegotiationStrategy 
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
       //如果媒体类型是 默认的所有 即 */* 则跳过 
      if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
         continue;
      }
      return mediaTypes;
   }
   //如果都不满足 则最后返回 默认所有 即*/*
   return MEDIA_TYPE_ALL_LIST;
}

对比上面的源码我们可以看到 Spring 做了很多层校验 来判断媒体类型是否为空。

理解可生成的媒体类型

使用@RequestMapping.produces() 属性 来指定MediaType 类型集合 影响 浏览器响应头 Content-Type 媒体类型映射。

  • 如果 @RequestMapping.produces() 存在,返回指定 MediaType 列表。
  • 否则,返回已注册的 HttpMessageConverter 列表中支持的 MediaType 列表。
  • 如果该列表与请求的媒体类型兼容,执行第一个兼容 HttpMessageConverter 的实现,默认
    @RequestMapping#produces 内容到响应头 Content-Type
    否则,抛出 HttpMediaTypeNotAcceptableException , HTTP Status Code : 415

这段源码会在稍后的源码分析中做详解。

理解可消费的媒体类型

使用@RequestMapping#consumes 属性,来设置兼容的MediaType 类型集合 过滤 请求头 Content-Type 媒体类型映射。

  • 如果请求头 Content-Type 媒体类型兼容 @RequestMapping.consumes() 属性,执行该 HandlerMethod
  • 否则 HandlerMethod 不会被调用。

这段源码会在稍后的源码分析中做详解。

REST内容协商流程

在讲述REST协商流程之前,我们先来了解一下 对于REST内容协商非常关键的两个解析器

  • 处理方法参数解析器(HandlerMethodArgumentResolver
    • 用于 HTTP 请求中解析 HandlerMethod 参数内容
  • 处理方法返回值解析器(HandlerMethodReturnValueHandler)
    • 用于 HandlerMethod 返回值解析为 HTTP 响应内容
流程图

对于协商流程,在这里贴一张图,流程大体就可以理解了,在结合下面的源码分析,相信很快就可以明白Spring的视图协商流程。

总体流程图.PNG

针对 上述第10步 转化HTTP消息 的详细流程图

详细流程图.PNG
调试前的代码准备

在这里需要增加一个简单的User对象以及对应请求的Controller

User.java

/**
 * 用户对象
 */
public class User {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

UserRestController.java

@RestController
public class UserRestController {


    //最终相应回浏览器的Content-Type 是 produces 的内容
    @PostMapping(value = "/user",
            consumes = "application/json;charset=UTF-8",
            produces = "application/json;charset=GBK")
    public User user(@RequestBody User user) {
        return user;
    }
}

添加完这两个类之后,让我们启动一下Spring-boot项目。

我们可以对比上面的总体流程图来对源码进行解读。我们使用PostMan来发送一个简单的请求。

postman1.PNG
postman2.PNG
首先我们先打开DispatcherServlet#doDispatch 方法。我们来关注下面两个方法
st2.png
首先我们先来看一下 步骤 2和3的对应方法
st1.png
接下来让我们进入到 步骤 4的对应方法中
st3.png
接下来就是我们的重头戏就是调用HandlerMethod,在这个阶段就包括了剩下的步骤,也就是步骤5~10。
st4.png
我们一步一步的往方法里进。
st5.png
我们接着进入到 RequestMappingHanlderAdapter 中的 handleInternal 方法。
st7.png
st8.png
st9.png
我们接着进入到 invokeAndHandle 方法
st10.png
st11.png
我们详细看一下如何解析方法参数的。
st12.png
这里我们重点看一下 argumentResolvers.supportsParameter(parameter) 这段代码 主要是判断 参数处理器是否支持解析传入的参数。
st13.png
判断完某个解析器(RequestResponseBodyMethodProcessor)是否支持解析,接下来就是具体解析的操作了。
st14.png
进入到 resolveArgument方法
st16.png
st15.png
在这里我们已经获取到了 入参的值,我们回到最初调用的方法然后反射调用方法。
st17.png
st18.png
st19.png
至此 我们步骤8之前的已经讲解完了。下面我们讲解一下返回值解析。我们重新回到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle 方法中。
st20.png
进入handleReturnValue 方法。
st21.png
我们来看一下 selectHandler() 这个方法
st22.png
选择好了返回值解析器 (RequestResponseBodyMethodProcesser),下面我们就看看如何解析返回值的。
细心的小伙伴可以发现 这个解析器即是 方法参数解析器 又是 返回值解析器。这里就不多做解释了 这是因为他即继承AbstractMessageConverterMethodArgumentResolver 抽象类又实现了 HandlerMethodReturnValueHandler 接口。我们再进入到里面一层的handleReturnValue
st23.png
st24.png
接下来的内容就是详细流程图中的内容了我们从第7步开始看,我们通过媒体类型来匹配HttpMessageConverter。
st25.png
st26.png
选择好转换器之后我们就可以进行转换了,我们看一下MappingJackson2HttpMessageConverter 是如何转换然后返回相应的。
st27.png
st28.png
st29.png
st30.png
最后,在PostMan中显示返回结果
st31.png
st32.png
通过响应头 我们也可以看到 @RequestMapping.produces() 的作用。

总结

虽然协商逻辑以及流程比较繁琐,但是在我们使用Spring的时候,这些功能给了我们很大的便利,至于 最后Jackson是如何序列化的,这里就不详细说明了。不属于本次内容的范畴。 别的不多说,继续努力吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容