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

   List headerValues = Arrays.asList(headerValueArray);
   try {
      //转换为Spring 内置媒体类型集合 
      List 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 resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    //循环遍历协商处理策略
   for (ContentNegotiationStrategy strategy : this.strategies) {
      //获取媒体类型 比如 HeaderContentNegotiationStrategy 
      List 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是如何序列化的,这里就不详细说明了。不属于本次内容的范畴。 别的不多说,继续努力吧。

你可能感兴趣的:(Spring--视图内容协商(三))