开发者

SpringBoot自定义内容协商的实现

目录
  • 现象演示
    • 创建实体类Dog
    • 创建Controller
    • 开启参数形式内容协商
    • 添加POM依赖 (让 SpringBoot 有返回相关格式数据的能力) 
    • 请求及响应
  • 源码解析
    • HandlerMethodReturnValueHandlerComposite#handleReturnValue
    • 选择 HandlerMethodReturnValueHandler
    • RequestResponseBodyMethodProcessor#supportsReturnType
    • 处理返回值
  • 扩展:自定义HttpMessageConverter处理自定义协议
    • 创建实体类Cat
    • 创建自定义HttpMessageConverter
    • 创建配置类
    • 接口及响应

现象演示

假设有一个需求是根据终端的不同,返回不同形式的数据,比如 PC 端需要以 html 格式返回数据,APandroidP、小程序端需要以 jsON 格式返回数据。这时我们是 coding 几个相似的接口?还是在一个接口里面做复杂判断处理?两个方案貌似都不理想,一旦需求改动,维护的东西就比较多,这时候我们利用 SpringBoot 的内容协商功能,就可以很好的简化逻辑,案例演示如下:

创建实体类Dog

public class Dog {
    private String name;

    public Dog() {

    }

    public Dog(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

创建Controller

@RestController
@RequestMapping("/contentpython_negotiation")
public class ContentNegotiationController {

    @GetMapping("/simple")
    public Dog getDog() {
        return new Dog("wangcai");
    }
}

开启参数形式内容协商

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

添加POM依赖 (让 SpringBoot 有返回相关格式数据的能力) 

<dependency>
    <groupId>com.fasterXML.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.16.1</version>
</dependency>

请求及响应

返回 JSON 格式的数据

php

SpringBoot自定义内容协商的实现

返回 HTML 格式的数据

SpringBoot自定义内容协商的实现

源码解析

HandlerMethodReturnValueHandlerComposite#handleReturnValue

SpringBoot自定义内容协商的实现

总体分为两步:

  • 选择一个 HandlerMethodReturnValueHandler 来处理当前返回值
  • 处理返回值

选择 HandlerMethodReturnValueHandler

SpringBoot自定义内容协商的实现

SpringBoot 会手动注册的一些 HandlerMethodReturnValueHandler ,一共有15 个 (SpringBoot 版本 2.6.13),我们主要关注 RequestResponseBodyMethodProcessor 这个handler。

SpringBoot自定义内容协商的实现

RequestResponseBodyMethodProcessor#supportsReturnType

SpringBoot自定义内容协商的实现

如果接口方法含有 @ResponseBody 注解,或者相关Controller上含有 @ResponseBody 注解,则RequestResponseBodyMethodProcessor 都可以处理

处理返回值

SpringBoot自定义内容协商的实现

writeWithMessageConverters 方法大概有以下几个步骤:

  • 获取 acceptableTypes
  • 获取 producibleTypes
  • 获取 mediaTypesToUse
  • 给 mediaTypesToUse 排序
  • 获取 selectedMediaType
  • 写出数据

获取 acceptableTypes

SpringBoot自定义内容协商的实现

分为两个分支:

  • Response 是否指定 Content-Type,并且 Content-Type 的类型不是 */*,则直接跳转到步骤6 (写出数据)
  • 获取 acceptableTypes

AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

默认情况下,只有 HeaderContentNegotiationStrategy ,因为我们在现象演示的时候,将属性 spring.mvc.contentnegotiation.favor-parameter 设置为 true,所以多出来一个 ParameterContentNegotiationStrategy 。如果 ParameterContentNegotiationStrategy 的 resolveMediaTypes 方法的返回值不为 null 且不为 MEDIA_TYPE_ALL_LIST,则以 ParameterContentNegotiationStrategy 的 resolveMediaTypes 方法返回值为准,即 ParameterContentNegotiationStrategy 的优先级高于 HeaderContentNegotiationStrategy

ParameterContentNegotiationStrategy#resolveMediaTypes

getMediaTypeKey

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

默认情况下,parameterName 的值为 format

修改 parameterName 默认值

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: custom_format

SpringBoot自定义内容协商的实现

resolveMediaTypeKey

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

就是以 parameterName 对应的属性值为 key, 从 URL 中获取 value,再以该 value 为 mediaTypeKey 从一个 map (mediaTypes)中获取 MediaType

mediaTypes的初始赋值

WebMvcConfigurationSupport#mvcContentNegotiationManager

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

因为我们在现象演示的时候添加了相关POM依赖,所有 mediaTypes 的 内容如下所示:

SpringBoot自定义内容协商的实现

即默认情况下,format 的参数值含义如下 :

  • json:acceptableTypes 为 [ application/json ]
  • xml:acceptableTypes 为 [ application/xml ]
  • 其他:acceptableTypes 为 [ */*]

也可以自定义key,配置如下所示,这时候如果参数携带 custom_format=lanyu,系统也会返回 application/xml 格式的数据

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: custom_format
      media-types: {lanyu : application/xml}

SpringBoot自定义内容协商的实现

HeaderContentNegotiationStrategy#resolveMediaTypes

SpringBoot自定义内容协商的实现

HeaderContentNegotiationStrategy 的 resolveMediaTypes 方法比较简单,就是获取请求头中 Accept 的值

获取 producibleTypes

SpringBoot自定义内容协商的实现

大概分为以下几种情况

Request 域的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 是否为 null

属性值不为 null:返回指定的 mediaTypes

属性值为 null

  • 是否存在 messageConverters 的 canWrite 方法返回 true
    • 存在:相关 messageConverters 的 getSupportedMediaTypes 方法返回值的集合
    • 不存在:MediaType.ALL

获取 mediaTypesToUse

SpringBoot自定义内容协商的实现

SpringBoot自定义内容协商的实现

主要通过 isCompatibleWith 方法判断  acceptableType 和 producibleType 是不是兼容的,主要有以下几种情况 :

produ编程客栈cibleType 为  null:返回false

producibleType 不为null

  • acceptableType 为&编程客栈nbsp;*/* 或  producibleType为 */* :返回true
  • acceptableType 和 producibleType 都不为 */*
    • acceptableType 和 producibleType 的 type 一致
      • acceptableType 和 producibleType 的 subtype 一致 : 返回true
      • acceptableType 和 producibleType 的 subtype 不一致
        • acceptableType 或 producibleType 的 subtype 的 isWildcardSubtype 方法返回true
          • acceptableType 或 producibleType 的 subtype 为 *:返回true
          • acceptableType 的 subtype 以 *+ 开头,并且 acceptableType 的后缀与producibleType一致:返回true
          • producibleType 的 subtype 以 *+ 开头,并且 producibleType 的后缀与acceptableType 一致:返回true
          • 其他情况:返回false
        • acceptableType 与 producibleType 的 subtype 的 isWildcardSubtype 方法都返回false:返回false
    • acceptableType 和 producibleType 的 type 不一致:返回false

给 mediaTypesToUse 排序

排序规则1:

  • 权重越大优先级越高
  • 参数个数越多优先级越高

如果排序规则1未判断出谁的优先级高,则使用排序规则2,排序规则2如下:

  • 权重越大优先级越高
  • type类型不为 *
  • subtype类型不为 * 或以 *+ 开头
  • 参数个数越多优先级越高

获取 selectedMediaType

遍历上一步经过排序的 mediaTypes,如果存在一个 mediaType 满足以下条件,则直接返回

  • type 类型不为 * ,subtype 类型不为 * 且不以 *+ 开头
  • MediaType 为 */* 或 application/*

写出数据

如果存在一个 HttpMessageConverter 的 canWrite 方法返回 true,则使用 HttpMessageConverter 的 write 方法写出数据

扩展:自定义HttpMessageConverter处理自定义协议

创建实体类Cat

public class Cat {
    private String name;

    public Cat() {

    }

    public Cat(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

创建自定义HttpMessageConverter

public class LanyuHttpMessageConverter implements HttpMessageConverter {

    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType) {
        if (Cat.class == clazz) {
            return true;
        }
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.singletonList(MediaType.parseMediaType("lanyu/custom"));
    }

    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        try {
            Cat cat = (Cat) o;
            String data = "cat = {name : " + cat.getName() + "}";

            OutputStream outputStream = outputMessage.getBody();
            outputStream.write(data.getBytes());
            outputStream.flush();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

创建配置类

@Configuration
public class MessageConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new LanyuHttpMessageConverter());
    }
}

接口及响应

@GetMapping("/custom_protocol")
public Cat getCustomProtocolData() {
    return new Cat("tom");
}

SpringBoot自定义内容协商的实现

我们也可以让我们自定义的协议支持 URL 传参形式,配置如下

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: custom_format
      media-types: {lanyu : lanyu/custom}

到此这篇关于SpringBoot自定义内容协商的实现的文章就介绍到这了,更多相关SpringBoot 内容协商内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜