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.xdnf.cn/news/1473075.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

步进电机改伺服电机

步进电机&#xff1a; 42&#xff1a;轴径5mm 57&#xff1a;轴径8mm 86&#xff1a;轴径14mm 【86CME120闭环】// 12牛米 伺服电机&#xff1a; 40&#xff1a; 60&#xff1a; 80&#xff1a; 86&#xff1a; ECMA——C 1 0910 R S 4.25A 轴径…

树状数组实现 查找逆序对

题意&#xff1a; 输入一个整数n。 接下来输入一行n个整数 。 1< < n ,且每个数字只会出现一次 题解&#xff1a; 按每个数字的大小存入树状数组 #include<bits/stdc.h> using namespace std; #define ll long long const int N10000; int arr[N]; ll a[N];…

Ros2中goal_handle状态SUCCEED及ACCEPTED在rclpy中的死循环

动作通信开发中&#xff0c;我使用rclpy编写代码&#xff0c;进行feedback等操作&#xff0c;前期实现的较为顺利&#xff0c;但所有逻辑均编写完后&#xff0c;却无法将goal_handle提交为succeed状态&#xff0c;以下是我的部分代码&#xff1a; self.server ActionServer(se…

利用Redis bitmap 实现签到案例

数据库实现 设计签到功能对应的数据库表 CREATE TABLE sign_record (id bigint NOT NULL AUTO_INCREMENT COMMENT 主键,user_id bigint NOT NULL COMMENT 用户id,year year NOT NULL COMMENT 签到年份,month tinyint NOT NULL COMMENT 签到月份,date date NOT NULL COMMENT 签…

GDP播放器 驱动视频播放器 PHP 系统源码 v4.4.3

最重要的是我们自己开发了源代码&#xff0c;因此无论您在使用此工具时遇到什么问题&#xff0c;我们都会快速解决。这个版本演示 分别支持PHP7.4/8.1/8.2三个版本 演示地址

8、Python中的字符串:字符串的基本操作及再论对象是否可变

引言 基于Python系列前面的几篇文章中&#xff0c;应该已经在脑海中对Python中的核心理念&#xff0c;有了一些认知&#xff0c;由于比较关键&#xff0c;这里再来回顾一下&#xff1a; 1、Python中一切皆对象2、对象分为可变对象和不可变对象3、区分重新赋值操作&#xff0c…

Dos(命令符窗口)命令

目录 1. 常用Windows组合键 2. 常用DOS命令 3. 复制操作 4. 当前路径 5. 查看电脑ip地址 6. 切换盘符: 7. 目录 8. 自动补齐 8. 进入某路径&#xff1a;cd 路径 9. 直接进入某个位置 10. 新建文件 11. 查看文件内容 12. 关机 13. 强行终止命令的执行&#xff1a;C…

java基于ssm+vue 旅游信息资源平台

1前台首页功能模块 旅游资源网站 &#xff0c;在系统首页可以查看首页、景点信息、酒店信息、客房信息、交流论坛、红色文化、个人中心、后台管理、客服等内容&#xff0c;如图1所示。 图1系统功能界面图 用户登录、用户注册&#xff0c;在注册页面可以填写用户名、密码、姓名…

如何第一次从零上传项目到GitLab

嗨&#xff0c;我是兰若&#xff0c;今天想给大家说下&#xff0c;如何上传一个完整的项目到与LDAP集成的GitLab&#xff0c;也就是说这个项目之前是不在git上面的&#xff0c;这是第一次上传&#xff0c;这样上传上去之后&#xff0c;其他小伙伴就可以根据你这个项目的git地址…

Maven 分模块设计与开发 继承

介绍 在 Maven 中进行分模块设计&#xff08;multi-module project&#xff09;&#xff0c;可以帮助将一个大型项目分解为更小、更易管理的模块。这种设计方式有助于提高项目的可维护性、复用性和团队协作效率。 继承关系 目录结构 引入父Maven 父坐标 在子项目中引入父亲…

什么是 DDoS 攻击及如何防护DDOS攻击

自进入互联网时代&#xff0c;网络安全问题就一直困扰着用户&#xff0c;尤其是DDOS攻击&#xff0c;一直威胁着用户的业务安全。而高防IP被广泛用于增强网络防护能力。今天我们就来了解下关于DDOS攻击&#xff0c;以及可以防护DDOS攻击的高防IP该如何正确选择使用。 一、什么是…

Windows Server 2016 搭建 网络负载平衡 服务

网络负载平衡功能的安装 添加角色 默认不动————功能 勾选上 < 网络负载平衡 > 在工具中————打开 < 网络负载平衡管理器 > 网络负载平衡群集创建 注意 : 提前 将两台 web 站点服务器 都安装好 < 网络负载平衡功能 > 右键 选择 ————新建群集 ——…

【ONLYOFFICE8.1】ONLYOFFICE8.1版本桌面编辑器测评

有宝子说office太贵&#xff0c;不适合个人和学生&#xff0c;而WPS不仅贵广告还多&#xff0c;那么有没有一款软件可以替代office和WPS呢&#xff1f;...当然有喽。当当当当&#xff01;它就是众望所归、备受好评的ONLYOFFICE。下面就和小编一起来探索和测评一下ONLYOFFICE8.1…

Apache Flink架构介绍

目录 一、Apache Flink架构组件栈 1.1 概述 1.2 架构图 1.3 架构分层组件说明 1.3.1 物理部署层 1.3.2 Runtime 核心层 1.3.3 API & Libraries层 二、Flink运行时架构 2.1 概述 2.2 架构图 2.3 架构角色和组件 2.3.1 Flink Clients客户端 2.3.2 JobManager 2.…

[SAP ABAP] 函数Function

Function函数与子例程类似&#xff0c;按照功能将代码模块化 我们可以使用事务码SE37查看需要使用的函数以及对该函数进行测试 我们也可以对STRING_SPLIT_AT_POSITION函数进行测试 1.函数调用 我们可以使用事务码SE38进入ABAP编辑器界面&#xff0c;使用"模式/Pattern&q…

微信小程序:rpx详解,使用 tailwindcss最佳方案rem转rpx

rpx详解 不管手机的屏幕宽度是多少&#xff0c;微信小程序都负责把这个宽度平均分成 750 份&#xff0c;而把这分好过的 750 份中的 1 份取名叫 1rpx。 tailwindcss rem转rpx 配置 rem 转 rpx 安装配置好了 tailwindcss&#xff0c;接下来一步便是配置 rem 转 rpx tailwind…

视频共享融合赋能平台LnyonCVS国标视频监控平台包含哪些功能

随着国内视频监控应用的迅猛发展&#xff0c;系统接入规模不断扩大。不同平台提供商的接入协议各不相同&#xff0c;导致终端制造商在终端维护时需要针对不同平台的软件版本提供不同的维护&#xff0c;资源造成了极大的浪费。 为响应国家对重特大事件通过视频监控集中调阅来掌…

2025深圳国际人工智能展览会

2025深圳国际人工智能展览会 Shenzhen International Artificial Intelligence Exhibition 2025 时间&#xff1a;2025年6月25-27日 地点&#xff1a;深圳国际会展中心&#xff08;宝安新馆&#xff09; 详询主办方陆先生 I38&#xff08;前三位&#xff09; I82I&#…

Selenium 切换 frame/iframe

环境&#xff1a; Python 3.8 selenium3.141.0 urllib31.26.19说明&#xff1a; driver.switch_to.frame() # 将当前定位的主体切换为frame/iframe表单的内嵌页面中 driver.switch_to.default_content() # 跳回最外层的页面# 判断元素是否在 frame/ifame 中 # 126 邮箱为例 # …

Linux shell编程学习笔记62: top命令 linux下的任务管理器

0 前言 top命令是Unix 和 Linux下常用的性能分析工具&#xff0c;提供了一个动态的、交互式的实时视图&#xff0c;显示系统的整体性能信息&#xff0c;以及正在运行的进程的相关信息&#xff0c;包括各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。 1 top命令…