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;}@Overridepublic 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

总体分为两步:

  1. 选择一个 HandlerMethodReturnValueHandler 来处理当前返回值
  2. 处理返回值

选择 HandlerMethodReturnValueHandler

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

RequestResponseBodyMethodProcessor#supportsReturnType

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

处理返回值

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

  1. 获取 acceptableTypes
  2. 获取 producibleTypes
  3. 获取 mediaTypesToUse
  4. 给 mediaTypesToUse 排序
  5. 获取 selectedMediaType
  6. 写出数据
获取 acceptableTypes

分为两个分支:

  • Response 是否指定 Content-Type,并且 Content-Type 的类型不是 */*,则直接跳转到步骤6 (写出数据)
  • 获取 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: trueparameter-name: custom_format

resolveMediaTypeKey

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

mediaTypes的初始赋值

WebMvcConfigurationSupport#mvcContentNegotiationManager

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

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

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

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

spring:mvc:contentnegotiation:favor-parameter: trueparameter-name: custom_formatmedia-types: {lanyu : application/xml}

HeaderContentNegotiationStrategy#resolveMediaTypes

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

获取 producibleTypes

大概分为以下几种情况

  • Request 域的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 是否为 null
    • 属性值不为 null:返回指定的 mediaTypes
    • 属性值为 null
      • 是否存在 messageConverters 的 canWrite 方法返回 true
        • 存在:相关 messageConverters 的 getSupportedMediaTypes 方法返回值的集合
        • 不存在:MediaType.ALL
获取 mediaTypesToUse

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

  • producibleType 为  null:返回false
  • producibleType 不为null
    • acceptableType 为 */* 或  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. 参数个数越多优先级越高

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

  1. 权重越大优先级越高
  2. type类型不为 *
  3. subtype类型不为 * 或以 *+ 开头
  4. 参数个数越多优先级越高
获取 selectedMediaType

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

  1. type 类型不为 * ,subtype 类型不为 * 且不以 *+ 开头
  2. 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;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\'' +'}';}
}

创建自定义HttpMessageConverter

public class LanyuHttpMessageConverter implements HttpMessageConverter {@Overridepublic boolean canRead(Class clazz, MediaType mediaType) {return false;}@Overridepublic boolean canWrite(Class clazz, MediaType mediaType) {if (Cat.class == clazz) {return true;}return false;}@Overridepublic List<MediaType> getSupportedMediaTypes() {return Collections.singletonList(MediaType.parseMediaType("lanyu/custom"));}@Overridepublic Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overridepublic 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 {@Overridepublic 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: trueparameter-name: custom_formatmedia-types: {lanyu : lanyu/custom}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/372025.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7.8作业

一、思维导图 二、 1】按值修改 2】按值查找&#xff0c;返回当前节点的地址 &#xff08;先不考虑重复&#xff0c;如果有重复&#xff0c;返回第一个&#xff09; 3】反转 4】销毁链表 //按值修改 int value_change(linklistptr H,datatype e,int value) {if(HNULL||empty(H…

二次元转向SLG,B站游戏的破圈之困

文 | 螳螂观察 作者 | 夏至 2023年是B站游戏的滑铁卢&#xff0c;尽管这年B站的游戏营收还有40多亿&#xff0c;但相比去年大幅下降了20%&#xff0c;整整少了10亿&#xff0c;这是过去5年来的最大跌幅&#xff0c;也是陈睿接管B站游戏业务一年以来&#xff0c;在鼻子上碰的第…

【Java系列】深入解析 Lambda表达式

简化这个代码 这个就是Lambda表达式,可以简化匿名内部类的写法 package lambda;public class demo2 {public static void main(String[] args) {//第二个参数是一个接口,所以我们在调用方法的时候,需要传递这个接口的实现类对象--接口多态// 但是这个实现类,我只要用一次,所以我…

SRS流媒体服务器概述

SRS/5.0(Bee) is a simple, high efficiency and realtime video server, supports RTMP, WebRTC, HLS, HTTP-FLV, SRT, MPEG-DASH and GB28181. 翻译&#xff1a;SRS/5.0(Bee)是一款简洁、高效、实时的视频服务器&#xff0c;支持RTMP、WebRTC、HLS、HTTP-FLV、SRT、MPEG-DAS…

广州银行多份招股书数据货不对板:内控风险难平,IPO曲折前行

作者|芋圆 来源|贝多财经 6月29日&#xff0c;广州银行第五次更新了招股说明书。 作为制造业大省的头部城商行&#xff0c;广州银行的发展一直备受关注。拆解可知&#xff0c;广州银行2023年在盈利能力、内控、资本充足性、资产质量等方面的表现&#xff0c;凸显了该行接下来…

鸿蒙开发HarmonyOS NEXT (三) 熟悉ArkTs (上)

一、自定义组件 1、自定义组件 自定义组件&#xff0c;最基础的结构如下&#xff1a; Component struct Header {build() {} } 提取头部标题部分的代码&#xff0c;写成自定义组件。 1、新建ArkTs文件&#xff0c;把Header内容写好。 2、在需要用到的地方&#xff0c;导入…

SpringBoot 启动流程六

SpringBoot启动流程六 这句话是创建一个上下文对象 就是最终返回的那个上下文 我们这个creatApplicationContext方法 是调用的这个方法 传入一个类型 我们通过打断点的方式 就可以看到context里面的东西 加载容器对象 当我们把依赖改成starter-web时 这个容器对象会进行…

Java对象通用比对工具

目录 背景 思路 实现 背景 前段时间的任务中&#xff0c;遇到了需要识别两个对象不同属性的场景&#xff0c;如果使用传统的一个个属性比对equals方法&#xff0c;会存在大量的重复工作&#xff0c;而且为对象新增了属性后&#xff0c;比对方法也需要同步修改&#xff0c;不方…

百度、谷歌、必应收录个人博客网站

主要是给各个搜索引擎提交你的sitemap文件&#xff0c;让别人能搜到你博客的内容。 主题使用的Butterfly。 生成sitemap 安装自动生成sitemap插件。 npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save在站点配置文件_config.yml…

自动化测试报告pytest-html样式美化

最近我将 pytest-html 样式优化了 一版 先看优化前&#xff1a; 优化后&#xff1a; 优化内容包括&#xff1a; 删除部分多余字段新增echart图表部分字体大小、行间距、颜色做了美化调整运行环境信息移至报告最后部分字段做了汉化处理&#xff08;没全部翻译是因为&#xf…

力扣爆刷第161天之TOP100五连刷71-75(搜索二叉树、二维矩阵、路径总和)

力扣爆刷第161天之TOP100五连刷71-75&#xff08;搜索二叉树、二维矩阵、路径总和&#xff09; 文章目录 力扣爆刷第161天之TOP100五连刷71-75&#xff08;搜索二叉树、二维矩阵、路径总和&#xff09;一、98. 验证二叉搜索树二、394. 字符串解码三、34. 在排序数组中查找元素的…

分享20个python学习要点

1 类型 在python中&#xff0c;一切都是对象&#xff0c;每个对象都有一个类型。 检查对象类型的常用方式。 <type> type(<el>) 或 <el>.__class__&#xff1a; 这两行代码都是获取对象的类型。type(<el>)会返回<el>的类型&#xff0c;而&l…

如何将资源前端通过 Docker 部署到远程服务器

作为一个程序员&#xff0c;在开发过程中&#xff0c;经常会遇到项目部署的问题&#xff0c;在现在本就不稳定的大环境下&#xff0c;前端开发也需要掌握部署技能&#xff0c;来提高自己的生存力&#xff0c;今天就详细说一下如何把一个前端资源放到远程服务器上面通过docker部…

C++入门(C语言过渡)

文章目录 前言一、C关键字二、命名空间三、C输入&输出四、缺省参数五、函数重载六、引用七、inline八、nullptr总结 前言 C是一种通用的、高级的、静态类型的编程语言&#xff0c;它在20世纪80年代由丹尼斯里奇创建的C语言基础上发展而来。以下是C发展的一些重要里程碑。 1…

kafka 生产者

生产者 生产者负责创建消息&#xff0c;然后将其投递到Kafka中。 负载均衡 轮询策略。随机策略。按照 key 进行hash。 Kafka 的默认分区策略&#xff1a;如果指定了 key&#xff0c;key 相同的消息会发送到同一个分区&#xff08;分区有序&#xff09;&#xff1b;如果没有…

const 修饰不同内容区分

1.修饰局部变量 const int a 1;int const a 1; 这两种是一样的 注意&#xff1a; const int b; 该情况下编译器会报错&#xff1a;常量变量"b”需要初始值设定项 将一个变量没有赋初始值直接const修饰后&#xff0c;在以后时无法更改内容的。 2.修饰常量字符串 a.…

【密码学基础】基于LWE(Learning with Errors)的全同态加密方案

学习资源&#xff1a; 全同态加密I&#xff1a;理论与基础&#xff08;上海交通大学 郁昱老师&#xff09; 全同态加密II&#xff1a;全同态加密的理论与构造&#xff08;Xiang Xie老师&#xff09; 现在第二代&#xff08;如BGV和BFV&#xff09;和第三代全同态加密方案都是基…

第10章:网络与信息安全

目录 第10章&#xff1a;网络与信息安全 网络概述 计算机网络概念 计算机网络的分类 网络的拓扑结构 ISO/OSI网络体系结构 网络互联硬件 物理层互联设备 数据链路层互联设备 网络层互联设备 应用层互联设备 网络的协议与标准 网络标准 TCP/IP协议族 网络接口层协…

浅谈反射机制

1. 何为反射&#xff1f; 反射&#xff08;Reflection&#xff09;机制指的是程序在运行的时候能够获取自身的信息。具体来说&#xff0c;反射允许程序在运行时获取关于自己代码的各种信息。如果知道一个类的名称或者它的一个实例对象&#xff0c; 就能把这个类的所有方法和变…

PyQt5显示QImage并将QImage转换为PIL图像保存到缓存

PyQt5显示QImage并将QImage转换为PIL图像保存到缓存 1、效果图 2、流程 1、获取摄像头资源,打开摄像头 2、截取图像 3、opencv读的通道是BGR,要转成RGB 4、往显示视频的Label里显示QImage 5、将QImage转换为PIL图像,并保存到缓存 6、获取图像中人脸信息3、代码 # -*- codin…