SpringBoot之内容协商

news2024/12/25 9:02:28

现象演示

假设有一个需求是根据终端的不同,返回不同形式的数据,比如 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

总体分为两步:

  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: true
      parameter-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: 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
      • 是否存在 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;
    }

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

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

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

相关文章

【Spring Boot】spring boot环境搭建

1、环境准备 JDK安装&#xff1a;确保安装了Java Development Kit (JDK) 1.8或更高版本。JDK是Java编程的基础&#xff0c;Spring Boot项目需要它来编译和运行。Maven或Gradle安装&#xff1a;选择并安装Maven或Gradle作为项目构建工具。Maven通过pom.xml文件来管理项目的依赖…

ThingsKit物联网平台功能解析

随着物联网技术的飞速发展&#xff0c;各种物联网平台应运而生&#xff0c;为设备管理和数据集成提供了强大的支持。ThingsKit物联网平台以其全面的功能和灵活的配置&#xff0c;成为行业中的一大亮点。本文将详细解析ThingsKit物联网平台的功能清单&#xff0c;带您深入了解该…

大模型时代的基础架构,大模型算力中心建设指南重磅来袭!

什么是最畅销商品&#xff1f;什么是高毛利商品&#xff1f; 我们来看一个例子&#xff1a; 一件T恤使用成本为100元的原料&#xff0c;价格为140元。另一件T恤使用成本为80元的原料&#xff0c;但在样式、颜色、图案的设计上比较有特色&#xff0c;价格也为140元。 当这两件…

AI Agent项目实战(03)-利用TTS技术让你的AI Agent发声

1 语音逻辑设计 一个AI Agent应用的语音逻辑设计流程图。 1.1 基本流程 - 用户 -> Agent -> 文本回答 最基础的交互模式。用户输入被传递给Agent&#xff0c;Agent生成文本回答。 1.2 添加语音功能 - 用户 -> Agent -> 文本回答|vTTS服务 -> MSTTS -> …

震撼学术界:扩散损失引领图像生成新革命

在人工智能的浪潮中,图像生成技术一直是热门话题。而今,何凯明教授及其团队在这一领域取得了突破性进展,他们提出的扩散损失(Diffusion Loss)技术,为图像生成带来了革命性的速度与效果提升! 传统图像生成的局限 长久以来,图像生成的自回归模型一直依赖于离散值标记…

政务单位网站SSL证书选择策略

在数字化快速发展的今天&#xff0c;政务单位网站作为政府与公众沟通的重要桥梁&#xff0c;其安全性和可信度显得尤为重要。SSL证书作为保障网站安全的重要手段&#xff0c;其选择对于政务单位网站来说至关重要。本文将探讨政务单位网站在选择SSL证书时应该考虑的因素&#xf…

SpringBoot实现图片添加水印(完整)

提示&#xff1a;昨天不是写了一个类似与图片添加水印的版本吗,今天来写一个带数据库&#xff0c;并且可以完整访问的版本 文章目录 目录 文章目录 引入库 配置文件 数据库配置 字段配置 索引配置 数据库表语句 启动文件 前端代码 整体代码目录 配置类AppConfig Contro…

行业分析---造车新势力之极氪汽车

1 前言 在之前的博客中&#xff0c;笔者撰写了多篇行业类分析的文章&#xff08;科技新能源&#xff09;&#xff1a; 《行业分析---我眼中的Apple Inc.》 《行业分析---马斯克的Tesla》 《行业分析---造车新势力之蔚来汽车》 《行业分析---造车新势力之小鹏汽车》 《行业分析-…

LangChain入门学习笔记(七)—— 使用检索提高生成内容质量

大模型训练使用的数据是开放的、广泛的&#xff0c;因此它显得更加的通用。然而在有些应用场景下&#xff0c;用户需要使用自己的数据使得大模型生成的内容更加贴切&#xff0c;也有时候用户的数据是敏感的&#xff0c;无法提供出来给大模型进行通用性的训练。RAG技术就是一种解…

mysql mgr集群断电重启

一、前言 mysql mgr集群所有节点都断电重启时&#xff0c;就会面临一个问题&#xff0c;应该怎么重新构建mgr集群 二、操作 查询所有节点的master状态 show master status; 查看同步状态&#xff0c;可以通过uuid知道是通过哪个节点进行同步的数据 查看所有节点的uuid&#x…

【CT】LeetCode手撕—82. 删除排序链表中的重复元素 II

题目 原题连接&#xff1a;82. 删除排序链表中的重复元素 II 1- 思路 模式识别1&#xff1a;已排序链表 ——> 判重逻辑 &#xff0c;涉及到 while 2- 实现 ⭐82. 删除排序链表中的重复元素 II——题解思路 class Solution {public ListNode deleteDuplicates(ListNode h…

2019:Cornfields

网址如下&#xff1a; OpenJudge - 2019:Cornfields 唉&#xff0c;最近沉迷法环&#xff0c;都没怎么学习了 话说这题的名字让我想起了《星际穿越》了&#xff08;玉米地呀玉米地&#xff09; 这是翻译过后的版本&#xff0c;题目要求我们帮助FJ找到放置玉米地的最佳位置&am…

深入STM32的ADC世界:从理论到实践,打造精准数据采集系统

摘要: 在嵌入式系统中&#xff0c;模拟信号的采集与处理至关重要。本文将深入浅出地介绍STM32的ADC模块&#xff0c;结合实际项目&#xff0c;详细阐述ADC的工作原理、配置方法以及数据处理技巧&#xff0c;并辅以Mermaid图表和代码示例&#xff0c;助你轻松掌握ADC应用开发。 …

回流焊常见缺陷

不润湿(Nonwetting)/润湿不良(Poor Wetting) 通常润湿不良是指焊点焊锡合金没有很好的铺展开来,从而无法得到良好的焊点并直接影响到焊点的可靠性。 产生原因: 1. 焊盘或引脚表面的镀层被氧化,氧化层的存在阻挡了焊锡与镀层之间的接触; 2. 镀层厚度不够或是加工不良,很…

arm-linux-gnueabihf-gcc:Command not found 解决办法

问题描述 使用虚拟机交叉编译程序&#xff0c;当使用了sudo去编译, 出现arm-linux-gnueabihf-gcc&#xff1a;Command not found的问题。明明已经安装并配置好环境了&#xff0c;发现还是提示找不到编译器。 原因分析&#xff1a; 特意去查了一下sudo方法&#xff0c;我们在sud…

chunk-vendors.js 优化

问题背景 在 App.vue 加入 web-vitals 性能监控指标并打印 import {onLCP, onINP, onCLS, onTTFB} from web-vitals/attribution;// Measure and log LCP as soon as its available. onLCP(console.log); onINP(console.log); onCLS(console.log); onTTFB(console.log);网页的…

linux开发常用工具和命令

文章目录 服务器文件挂载服务器终端挂载vscode编辑服务器代码linux开发常用命令dulnllobjdump 服务器文件挂载 在本地操作服务器文件。 在文件夹右侧‘其他位置‘’—>‘链接到服务器s’ sftp://usernameip/目录&#xff0c;回车后输入密码即可在左侧看到映射的服务器文件夹…

网络基础-RIP协议

RIP&#xff08;Routing Information Protocol&#xff09;是一个基于距离矢量的动态路由协议&#xff0c;常用于小型到中型网络。RIP是较早的路由协议之一&#xff0c;具有简单易用的特点。以下是关于RIP协议的详细介绍&#xff1a; RIP的主要特点 ①使用跳数&#xff08;ho…

自己分析的逆向案例八——中国新烟商盟登录密码加密

网址&#xff1a;会员登录 - 新商盟 登陆界面分析&#xff0c;找到登录接口&#xff0c;跟栈分析。 没有异步&#xff0c;也没有webpack,很简单的跟栈 打上断点&#xff0c;逐步网上找&#xff0c;找到$ajax老朋友 上面就有对密码加密的部分&#xff0c;是一个RSA加密。 b 被…

认识100种电路之耦合电路

在电子电路的世界中&#xff0c;耦合电路宛如一座精巧的桥梁&#xff0c;连接着各个功能模块&#xff0c;发挥着至关重要的作用。 【为什么电路需要耦合】 在复杂的电子系统中&#xff0c;不同的电路模块往往需要协同工作&#xff0c;以实现特定的功能。然而&#xff0c;这些模…