当前位置: 首页 > news >正文

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转换器使用方式

相关文章:

  • 香港Web3媒体:Techub News
  • 动手学深度学习(Pytorch版)代码实践-深度学习基础-01基础函数的使用
  • 价值飙升30%,AI PC拉动半导体出货潮
  • 今日好料推荐(大数据湖体系规划)
  • Codeforces Round 947 (Div. 1 + Div. 2) D. Paint the Tree 题解 DFS
  • 轧钢测径仪分析软件,四大图表带来产线新视角!
  • 云下到云上,丽迅物流如何实现数据库降本50% | OceanBase案例
  • 数字营销:以大数据作引擎,推动企业全面数字化升级
  • FFmpeg编解码的那些事(2)
  • 包和final
  • HaloDB 的 Oracle 兼容模式
  • 一个月速刷leetcodeHOT100 day13 二叉树结构 以及相关简单题
  • 解决vue版本不一致导致不能正常编译
  • 学习笔记——动态路由协议——OSPF(OSPF基本术语)
  • Sylvester矩阵、子结式、辗转相除法的三者关系(第二部分)
  • 「面试题」如何实现一个圣杯布局?
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【comparator, comparable】小总结
  • Android 控件背景颜色处理
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • python docx文档转html页面
  • Vue学习第二天
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 开源地图数据可视化库——mapnik
  • 区块链共识机制优缺点对比都是什么
  • 如何利用MongoDB打造TOP榜小程序
  • 正则与JS中的正则
  • 阿里云API、SDK和CLI应用实践方案
  • ​ssh免密码登录设置及问题总结
  • ​低代码平台的核心价值与优势
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • $forceUpdate()函数
  • (4) PIVOT 和 UPIVOT 的使用
  • (备份) esp32 GPIO
  • (苍穹外卖)day03菜品管理
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (一)RocketMQ初步认识
  • (转)linux 命令大全
  • **CentOS7安装Maven**
  • .Mobi域名介绍
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .net6Api后台+uniapp导出Excel
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .Net下的签名与混淆
  • .NET中统一的存储过程调用方法(收藏)
  • ::前边啥也没有
  • @Controller和@RestController的区别?
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [ vulhub漏洞复现篇 ] Apache APISIX 默认密钥漏洞 CVE-2020-13945
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [C++]类和对象(中)