java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot 内容协商

SpringBoot自定义内容协商的实现

作者:一只懒鱼a

在Spring Boot中,内容协商是一种机制,它允许服务器根据客户端的请求选择返回不同的表示形式,本文就来详细的介绍一下SpringBoot自定义内容协商的实现,感兴趣的可以了解一下

现象演示

假设有一个需求是根据终端的不同,返回不同形式的数据,比如 PC 端需要以 HTML 格式返回数据,APP、小程序端需要以 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("/content_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 格式的数据

返回 HTML 格式的数据

源码解析

HandlerMethodReturnValueHandlerComposite#handleReturnValue

总体分为两步:

选择 HandlerMethodReturnValueHandler

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

RequestResponseBodyMethodProcessor#supportsReturnType

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

处理返回值

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

获取 acceptableTypes

分为两个分支:

AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes

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

ParameterContentNegotiationStrategy#resolveMediaTypes

getMediaTypeKey

默认情况下,parameterName 的值为 format

修改 parameterName 默认值

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

resolveMediaTypeKey

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

mediaTypes的初始赋值

WebMvcConfigurationSupport#mvcContentNegotiationManager

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

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

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

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

HeaderContentNegotiationStrategy#resolveMediaTypes

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

获取 producibleTypes

大概分为以下几种情况

Request 域的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 是否为 null

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

属性值为 null

获取 mediaTypesToUse

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

producibleType 为  null:返回false

producibleType 不为null

给 mediaTypesToUse 排序

排序规则1:

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

获取 selectedMediaType

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

写出数据

如果存在一个 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");
}

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

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

到此这篇关于SpringBoot自定义内容协商的实现的文章就介绍到这了,更多相关SpringBoot 内容协商内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文