RestTemplet 自定义消息转换器总结

一、请求流程

在RestTemplet 请求中,请求发送一个 HTTP 请求时,RestTemplet 会根据请求中的内容类型(Content-Type)选择合适的 HttpMessageConverter 来处理请求体的数据。同样地,当服务器返回一个 HTTP 响应时,RestTemplet 会根据请求中的 Accept 头部信息选择合适的 HttpMessageConverter 来处理响应体的数据。

处理过程和SpringMVC的处理过程类似
在这里插入图片描述

二、spring RestTemplate自定义HttpMessageConverter

项目中通过RestTemplate调用其他接口,接口返回的Content-Type是text/json;charset=utf-8,导致调用时报错: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [text/json;charset=utf-8],我们可以通过自定义HttpMessageConverter来解决。

问题分析
我们使用RestTemplate一般会指定返回的数据转换成何种类型,比如如下使用方式,我们告诉RestTemplate将返回的数据转换成A.class的对象。

restTemplate.postForObject("http://localhost:8080/rt/testUrl", entity, A.class);

我们知道,http返回的数据只是一些二进制数据而已,如何将这些二进制数据转换成指定的类型?这就要用到HttpMessageConverter了。跟踪源码我们可以知道,RestTemplate是在org.springframework.web.client.HttpMessageConverterExtractor#extractData这个方法中对数据进行转换的。

为方便后面分析,先把这个方法的代码贴在这里。

	public T extractData(ClientHttpResponse response) throws IOException {MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {return null;}MediaType contentType = getContentType(responseWrapper);try {for (HttpMessageConverter<?> messageConverter : this.messageConverters) {if (messageConverter instanceof GenericHttpMessageConverter) {GenericHttpMessageConverter<?> genericMessageConverter =(GenericHttpMessageConverter<?>) messageConverter;if (genericMessageConverter.canRead(this.responseType, null, contentType)) {if (logger.isDebugEnabled()) {ResolvableType resolvableType = ResolvableType.forType(this.responseType);logger.debug("Reading to [" + resolvableType + "]");}return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);}}if (this.responseClass != null) {if (messageConverter.canRead(this.responseClass, contentType)) {if (logger.isDebugEnabled()) {String className = this.responseClass.getName();logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");}return (T) messageConverter.read((Class) this.responseClass, responseWrapper);}}}}catch (IOException | HttpMessageNotReadableException ex) {throw new RestClientException("Error while extracting response for type [" +this.responseType + "] and content type [" + contentType + "]", ex);}throw new UnknownContentTypeException(this.responseType, contentType,responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(),responseWrapper.getHeaders(), getResponseBody(responseWrapper));}

这里的思路很清楚,先获取response的contentType,然后遍历所有HttpMessageConverter,调用HttpMessageConverter方法的canRead方法判断当前HttpMessageConvert能不能转换这个response,如果可以,就直接调用read方法,并返回结果。从这里也可以看出,HttpMessageConverter的顺序也很重要,一旦发现某个HttpMessageConverter可用,后面的就不会再判断了。

同时注意到,方法会先判断HttpMessageConverter是否是GenericHttpMessageConverter类型的,如果是,就调用canRead方法进行判断是否可以读取当前response。GenericHttpMessageConverter是HttpMessageConverter的子接口,可以支持泛型。我们平时用泛型还是挺多的,所以我们就通过实现GenericHttpMessageConverter来演示如果自定义HttpMessageConverter来支持ContentType为text/json的接口。

问题解决
先把测试代码写一下。

测试接口

@Controller
@RequestMapping("/rt")
public class RtTestController {@Autowiredprivate RestTemplate restTemplate;@ResponseBody@RequestMapping("test")public Object test() {HttpHeaders headers = new HttpHeaders();HttpEntity entity = new HttpEntity(null, headers);// 调用testUrl接口,testUrl接口返回的ContentType是text/jsonObject o = restTemplate.postForObject("http://localhost:8080/rt/testUrl", entity, Object.class);return o;}@RequestMapping("testUrl")public ResponseEntity testUrl() {HttpHeaders headers = new HttpHeaders();// 指定返回ContentType为text/jsonheaders.setContentType(MediaType.valueOf("text/json;charset=utf-8"));// 注意这里返回的数据为{},因为一会我们要用json工具解析数据,所以要返回符合json格式的数据。ResponseEntity entity = new ResponseEntity("{}", headers, HttpStatus.OK);return entity;}
}

配置RestTemplate

@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();return restTemplate;}
}

三、通过实现GenericHttpMessageConverter自定义HttpMessageConverter

 
public class MyCustomGenericHttpMessageConverter implements GenericHttpMessageConverter<Object> {@Overridepublic boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {// 如果MediaType是text/json类型,返回truereturn MediaType.valueOf("text/json").includes(mediaType);}@Overridepublic Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {// 利用json工具将数据转换成java对象,这里用的是hutool的json工具类。String content = IoUtil.read(inputMessage.getBody(), Charset.defaultCharset());return JSONUtil.toBean(content, type, false);}@Overridepublic boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {// 这里只演示read,所以canWrite返回false.return false;}@Overridepublic void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {}@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType) {// GenericHttpMessageConverter用的是canRead(Type type, Class<?> contextClass, MediaType mediaType)// 所以这个方法可以不用管return false;}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {return false;}@Overridepublic List<MediaType> getSupportedMediaTypes() {return null;}@Overridepublic Object read(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overridepublic void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {}
}

将自定义的HttpMessageConverter添加到RestTemplate中

 
@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.getMessageConverters().add(new MyCustomGenericHttpMessageConverter());return restTemplate;}
}

这样配置好后,再调用test接口就不会报错了。

四、通过继承AbstractGenericHttpMessageConverter自定义HttpMessageConverter

直接实现GenericHttpMessageConverter我们需要重写很多方法,spring给我们提供了一个更方便的抽象类AbstractGenericHttpMessageConverter,也可以通过继承这个类来自定义,代码如下。

 
public class MyCustomGenericHttpMessageConverter2 extends AbstractGenericHttpMessageConverter<Object> {// 通过构造函数指定支持的MediaTypepublic MyCustomGenericHttpMessageConverter2(MediaType mediaType) {super(mediaType);}@Overrideprotected void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {// 这里只演示read}@Overrideprotected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overridepublic Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {// 利用json工具将数据转换成java对象,这里用的是hutool的json工具类。String content = IoUtil.read(inputMessage.getBody(), Charset.defaultCharset());return JSONUtil.toBean(content, type, false);}
}

配置RestTemplate

@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();MyCustomGenericHttpMessageConverter2 converter = new MyCustomGenericHttpMessageConverter2(MediaType.valueOf("text/json"));restTemplate.getMessageConverters().add(converter);return restTemplate;}
}

五、通过BeanPostProcessor扩展RestTemplate

这里再多说一点吧,由于我们开发使用的是公司的框架,框架中已经封装好了RestTemplate,这时再自定义一个RestTemplate就不合适了,我们可以通过BeanPostProcessor来扩展已有的RestTemplate,代码如下。

@Component
public class RestTemplateBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof RestTemplate) {RestTemplate restBean = (RestTemplate) bean;MyCustomGenericHttpMessageConverter2 converter = new MyCustomGenericHttpMessageConverter2(MediaType.valueOf("text/json"));restBean.getMessageConverters().add(converter);}return bean;}
}

六、Error while extracting response for type [] and content type [],json返回值被解析为xml

————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/mofeimo110/article/details/122087117
在使用restTemplate请求restful接口时,在特定情况下总会将返回的json数据解析为xml数据然后处理,接着就会爆出标题中的错误:

Error while extracting response for type [] and content type [application/xml;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected character '5' (code 53) in content after '<' (malformed start element?).at [row,col {unknown-source}]: [1,15395]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected character '5' (code 53) in content after '<' (malformed start element?).

根据错误信息来看,似乎响应头标记了返回类型为[application/xml;charset=UTF-8],然而实际情况是所有的返回数据都是[application/json;charset=UTF-8]。

跟踪restTemplate源码,发现由new RestTemplate(httpRequestFactory())创建的实例会有7个converter:
在这里插入图片描述
继续跟踪restTemplate的exchange,当对response进行类型转换时,会迭代当前实例中所有的converter,然后选择一个支持当前类型的converter执行,使用canRead来判断:

在这里插入图片描述
此时就发现了问题,在特定情况下,响应头的contentType被读作了"application/xml",然而此时的真实数据仍然为json格式。所以将用于xml格式的converter删除,则迭代器会寻找下一个可执行的converter即MappingJackson2HttpMessageConverter。或者将二者顺序换一下,降低xml的优先级。

解决办法:

方案1:删除xml的转换器

    @Beanpublic RestTemplate restTemplate() {RestTemplate template = new RestTemplate(httpRequestFactory());// 排除掉xml的解析converter,避免将json数据当做xml解析List<HttpMessageConverter<?>> collect = template.getMessageConverters().stream().filter(m -> !(m instanceof MappingJackson2XmlHttpMessageConverter)).collect(Collectors.toList());template.setMessageConverters(collect);return template;}

方案2:降低xml转换器优先级

    @Beanpublic RestTemplate restTemplate() {RestTemplate template = new RestTemplate(httpRequestFactory());// 将xml解析的优先级调低int xml = 0, json = 0;List<HttpMessageConverter<?>> messageConverters = template.getMessageConverters();for (int i = 0; i < messageConverters.size(); i++) {HttpMessageConverter<?> h = messageConverters.get(i);if (h instanceof MappingJackson2XmlHttpMessageConverter) {xml = i;} else if (h instanceof MappingJackson2HttpMessageConverter) {json = i;}}Collections.swap(template.getMessageConverters(), xml, json);return template;}

一图搞定 RestTemplete HttpMessageConvert 消息解码 及 常见中文乱码问题

自定义HttpMessageConverter实现RestTemplate的exchange方法返回自定义格式数据

Spring/Boot/Cloud系列知识:HttpMessageConverter转换器使用方式

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

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

相关文章

每天写两道(二)LRU缓存、数组中最大的第k个元素

146.LRU 缓存 . - 力扣&#xff08;LeetCode&#xff09; 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存…

【教程】Linux 安装 kkFileView 文档在线预览项目 及优化

【教程】Linux 安装 kkFileView 文档在线预览项目 官网 kkFileView - 在线文件预览 (keking.cn) 安装包 可以直接下载成品 也可以下载source 源码 自己编译 kkFileView 发行版 - Gitee.com 打开IDEA 然后先clear 再install 然后在 file-online-preview\server\target 目录…

canfd与can2.0关系

canfd是can2.0的升级版&#xff0c; 支持canfd的设备就支持can2.0&#xff0c;但can2.0的设备不支持canfd 参考 是选CAN接口卡还是CANFD接口卡_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Hh411K7Zn/?spm_id_from333.999.0.0 哪些STM32有CANFD外设 STM32G0, STM…

pycharm连接阿里云服务器过程记录

因为不想用自己的电脑安装anaconda环境,所以去查了一下怎么用服务器跑代码,试着用pycharm连接阿里云服务器,参考了很多博客,自己简单配置了一下,记录一下目前完成的流程. 主要是:阿里云服务器的远程登录和安装anaconda,以及怎么用pycharm连接阿里云服务器上的解释器. 小白刚开始…

Unity开发——XLua热更新之Hotfix配置(包含xlua获取与导入)

一、Git上获取xlua 最新的xlua包&#xff0c;下载地址链接&#xff1a;https://github.com/Tencent/xLua 二、Unity添加xlua 解压xlua压缩包后&#xff0c;将xlua里的Assets里的文件直接复制进Unity的Assets文件夹下。 成功导入后&#xff0c;unity工具栏会出现xlua选项。 …

【国产中颖】SH79F9202U单片机驱动LCD段码液晶学习笔记

1. 引言 因新公司之前液晶数显表产品单片机一直用的是 C51单片机(SH79F9202U9)&#xff0c;本人之前没有接触过这款单片机&#xff0c;为了维护老产品不得不重新研究研究这款单片机。 10位ADC LCD的增强型8051微控制器 SH79F9202是一种高速高效率8051可兼容单片机。在同样振…

QT7_视频知识点笔记_67_项目练习(页面以及对话框的切换,自定义数据类型,DB数据库类的自定义及使用)

视频项目&#xff1a;7----汽车销售管理系统&#xff08;登录&#xff0c;品牌车管理&#xff0c;新车入库&#xff0c;销售统计图表&#xff09;-----项目视频没有&#xff0c;代码也不全&#xff0c;更改项目练习&#xff1a;学生信息管理系统。 学生信息管理系统&#xff1…

【小技巧】Keil C51 报错“*** ERROR L107: ADDRESS SPACE OVERFLOW****

软件&#xff1a;Keil C51 C51V961版本 电脑&#xff1a;Win10 报错提示&#xff1a; compiling System.c... linking... *** ERROR L107: ADDRESS SPACE OVERFLOW SPACE: DATA SEGMENT: ?DT?LCD LENGTH: 0034H Program Size: data174.0 xdata17 code1205 Target not create…

VMware安装Ubuntu系统(超详细)

一.Ubuntu官网下载镜像 Ubuntu官网&#xff1a;Enterprise Open Source and Linux | Ubuntu 二.安装Ubuntu系统 选择文件->创建虚拟机新建虚拟机&#xff08;ControlN&#xff09;&#xff0c;这里直接选择典型即可 选择稍后安装系统 选择linux Ubuntu 64位 填写虚拟机名称…

【机器学习】支持向量机(SVM)

一、概述 支持向量机&#xff08;Support Vector Machine&#xff0c;简称SVM&#xff09;是一种对数据进行二分类的广义线性分类器&#xff0c;是一种监督学习算法&#xff0c;其决策边界是对学习样本求解的最大边距超平面。 SVM使用铰链损失函数计算经验风险并在求解系统中…

什么叫USDT(泰达币)的前世今生!

一、引言 在数字货币的世界里&#xff0c;USDT&#xff08;Tether USDT&#xff09;以其独特的稳定机制&#xff0c;成为了连接传统金融市场与加密货币市场的桥梁。本文将带您了解USDT的诞生背景、发展历程、技术特点以及未来展望。 二、USDT的诞生背景 USDT是Tether公司推出…

关于 Spring 是什么

Spring 是什么 我们通常所说的 Spring 指的是 Spring Framework&#xff08;Spring 框架&#xff09;&#xff0c;它是⼀个开源框架&#xff0c;有着活跃⽽庞⼤的社区&#xff0c;这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景&#xff0c;它可以让 Java 企业级的…

gitlab 创建 ssh 和 token

文章目录 一、创建ssh key二、将密钥内容复制到gitlab三、创建token 一、创建ssh key 打开控制台cmd&#xff0c;执行命令 ssh-keygen -t rsa -C xxxxx xxxxx是你自己的邮箱 C:\Users\xx\.ssh 目录下会创建一个名为id_rsa.pub的文件&#xff0c;用记事本打开&#xff0c;并…

Vue3解决“找不到模块“@/components/xxx.vue”或其相应的类型声明”

文章目录 前言背景问题描述解决方案总结 前言 在使用 Vue 3 开发项目时&#xff0c;遇到“找不到模块 ‘/components/xxx.vue’ 或其相应的类型声明”的错误是一个常见问题。这通常与 TypeScript 和模块解析相关的配置不当有关。本文将详细介绍如何解决此问题&#xff0c;确保…

XDebug配置极简教程,phpstorm实现http请求断点调试

写这篇的文章的初衷:网络上配置XDebug的文章有很多,XDebug也有官方的文档, PhpStorm也有官方的文档,为什么还要写那? 相信不少人,都有一种感觉,虽然教程很多,但是按教程走一遍,自己的确不能正常调试。 问题出在下面几个方面: 1. 对调试过程中,没有一定的认识,因此…

Pandas-中axis的用法

在Pandas中&#xff0c;min(axis)方法是计算DataFrame或Series中每行或每列的最小值的函数。该函数可以接受一个参数axis&#xff0c;用于指定计算最小值的方向。当axis0时&#xff0c;表示沿着行的方向计算最小值&#xff1b;当axis1时&#xff0c;表示沿着列的方向计算最小值…

【数据结构与算法 | 链表篇】力扣876

1. 力扣876 : 链表的中间节点 (1). 题 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表…

YOLOv10真正实时端到端目标检测(原理介绍+代码详见+结构框图)| YOLOv10如何训练自己的数据集(NEU-DET为案列)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文主要内容:真正实时端到端目标检测&#xff08;原理介绍代码详见结构框图&#xff09;| YOLOv10如何训练自己的数据集&#xff08;NEU-DET为案列&#xff09; 博主简介 AI小怪兽&#xff0c;YOLO骨灰级玩家&#xff0c;1&#xff0…

QT截图程序,可多屏幕截图二,增加调整截图区域功能

上一篇QT截图程序&#xff0c;可多屏幕截图只是实现了最基本的截图功能&#xff0c;虽然能用但是缺点也有&#xff0c;没办法更改选中的区域&#xff0c;这在实际使用时不太方便。这篇增加了这个功能。先看看效果。 实现代码为&#xff1a; 头文件 #ifndef MASKWIDGET_H #de…

NTLite深度Windows系统镜像文件修改定制

计算机爱好者和技术宅的圈子里,NTLite是一个广受欢迎的名字,一款强大的Windows系统定制工具,允许用户对Windows安装镜像进行深度修改,从而打造出一个更加个性化、高效且精简的操作系统。无论是为了优化系统性能、移除不必要的组件,还是集成最新的更新和驱动,NTLite都能成…