美文网首页
Spring--视图内容协商(三)

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

作者: NealLemon | 来源:发表于2018-12-29 22:50 被阅读0次

    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是如何序列化的,这里就不详细说明了。不属于本次内容的范畴。 别的不多说,继续努力吧。

    相关文章

      网友评论

          本文标题:Spring--视图内容协商(三)

          本文链接:https://www.haomeiwen.com/subject/wblmlqtx.html