RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type..
使用 Spring Boot 写项目,需要用到微信接口获取用户信息。
在 Jessey 和 Spring RestTemplate 两个 Rest 客户端中,想到尽量不引入更多的东西,然后就选择了 Spring RestTemplate 作为 网络请求的 Client,然后就被微信接口摆了一道,然后踩了一个 RestTemplate 的坑。
二、第一个坑:被微信摆了一道
报错信息是:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.solar.app.model.weixin.WxBaseUserInfo] and content type [text/plain]
之所以被微信摆了一道,是因为微信接口文档虽说返回的是 Json 数据,但是同时返回的 Header 里面的 Content-Type 值确是 text/plain 的!!
最终结果就是导致 RestTemplate 把数据从 HttpResponse 转换成 Object 的时候,找不到合适的 HttpMessageConverter 来转换!
我使用 RestTemplate 时配置 Bean 时使用默认的构造函数:
@Bean
RestTemplate restTemplate(){return new RestTemplate();
}
继续看 RestTemplate() 默认构造函数都干了啥:
/*** Create a new instance of the {@link RestTemplate} using default settings.* Default {@link HttpMessageConverter}s are initialized.*/
public RestTemplate() {this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());this.messageConverters.add(new ResourceHttpMessageConverter());this.messageConverters.add(new SourceHttpMessageConverter<Source>());this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {this.messageConverters.add(new AtomFeedHttpMessageConverter());this.messageConverters.add(new RssChannelHttpMessageConverter());}if (jackson2XmlPresent) {this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());}else if (jaxb2Present) {this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());}if (jackson2Present) {this.messageConverters.add(new MappingJackson2HttpMessageConverter());// tag1}else if (gsonPresent) {this.messageConverters.add(new GsonHttpMessageConverter());}
}
可以看到,RestTemplate() 默认构造函数设置了一系列 HttpMessageConverter。
我的项目里引入了 com.fasterxml.jackson,所以 RestTemplate() 会构造一个 MappingJackson2HttpMessageConverter 加到它的 messageConverters 中,即上面的代码:【tag1】
继续看 MappingJackson2HttpMessageConverter() 默认构造函数:
/*** Construct a new {@link MappingJackson2HttpMessageConverter} using default configuration* provided by {@link Jackson2ObjectMapperBuilder}.*/
public MappingJackson2HttpMessageConverter() {this(Jackson2ObjectMapperBuilder.json().build());
}/*** Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.* You can use {@link Jackson2ObjectMapperBuilder} to build it easily.* @see Jackson2ObjectMapperBuilder#json()*/
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
可以看到,默认构造的 MappingJackson2HttpMessageConverter 中的 supportedMediaTypes 只支持:application/json 的 MediaType。
再看 RestTemplate 请求的流程,会执行到这里:
/*** Execute the given method on the provided URI.* <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};* the response with the {@link ResponseExtractor}.* @param url the fully-expanded URL to connect to* @param method the HTTP method to execute (GET, POST, etc.)* @param requestCallback object that prepares the request (can be {@code null})* @param responseExtractor object that extracts the return value from the response (can be {@code null})* @return an arbitrary object, as returned by the {@link ResponseExtractor}*/
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,ResponseExtractor<T> responseExtractor) throws RestClientException {Assert.notNull(url, "'url' must not be null");Assert.notNull(method, "'method' must not be null");ClientHttpResponse response = null;try {ClientHttpRequest request = createRequest(url, method);if (requestCallback != null) {requestCallback.doWithRequest(request);}response = request.execute();handleResponse(url, method, response);if (responseExtractor != null) {return responseExtractor.extractData(response);// tag2}else {return null;}}catch (IOException ex) {String resource = url.toString();String query = url.getRawQuery();resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource);throw new ResourceAccessException("I/O error on " + method.name() +" request for \"" + resource + "\": " + ex.getMessage(), ex);}finally {if (response != null) {response.close();}}
}
从 HttpResponse 中获取数据实际是执行 【tag2】。这个操作由 HttpMessageConverterExtractor 类来完成:
@Override
@SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {return null;}MediaType contentType = getContentType(responseWrapper);// tag3, 微信返回的是 text/plainfor (HttpMessageConverter<?> messageConverter : this.messageConverters) {if (messageConverter instanceof GenericHttpMessageConverter) {GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter;if (genericMessageConverter.canRead(this.responseType, null, contentType)) {// tag4if (logger.isDebugEnabled()) {logger.debug("Reading [" + this.responseType + "] as \"" +contentType + "\" using [" + messageConverter + "]");}return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);}}if (this.responseClass != null) {if (messageConverter.canRead(this.responseClass, contentType)) {if (logger.isDebugEnabled()) {logger.debug("Reading [" + this.responseClass.getName() + "] as \"" +contentType + "\" using [" + messageConverter + "]");}return (T) messageConverter.read((Class) this.responseClass, responseWrapper);}}}throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +"for response type [" + this.responseType + "] and content type [" + contentType + "]");
}
【tag4】处的代码用于判断 MappingJackson2HttpMessageConverter 是否支持 【tag3】 类型的 MediaType。
AbstractJackson2HttpMessageConverter:
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {if (!canRead(mediaType)) {// tag5return false;}JavaType javaType = getJavaType(type, contextClass);if (!logger.isWarnEnabled()) {return this.objectMapper.canDeserialize(javaType);}AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();if (this.objectMapper.canDeserialize(javaType, causeRef)) {return true;}logWarningIfNecessary(javaType, causeRef.get());return false;
}
AbstractHttpMessageConverter:
/*** Returns {@code true} if any of the {@linkplain #setSupportedMediaTypes(List)* supported} media types {@link MediaType#includes(MediaType) include} the* given media type.* @param mediaType the media type to read, can be {@code null} if not specified.* Typically the value of a {@code Content-Type} header.* @return {@code true} if the supported media types include the media type,* or if the media type is {@code null}*/
protected boolean canRead(MediaType mediaType) {if (mediaType == null) {return true;}for (MediaType supportedMediaType : getSupportedMediaTypes()) {if (supportedMediaType.includes(mediaType)) {return true;}}return false;
}
一路追踪下来,可以确定,只要让 MappingJackson2HttpMessageConverter 能处理头部 Content-Type 为 text/plain 类型的 Json 返回值的话,我们就能让其帮我们把 Json 反序列化成我们要的对象。
我们继承 MappingJackson2HttpMessageConverter 并在构造过程中设置其支持的 MediaType 类型即可:
public class WxMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {public WxMappingJackson2HttpMessageConverter(){List<MediaType> mediaTypes = new ArrayList<>();mediaTypes.add(MediaType.TEXT_PLAIN);setSupportedMediaTypes(mediaTypes);// tag6}
}
【tag6】的代码,会覆盖其默认的 MediaType 设置。
然后把这个 WxMappingJackson2HttpMessageConverter 追加到 RestTemplate 的 messageConverters 消息转换链中去:
@Bean
RestTemplate restTemplate(){RestTemplate restTemplate = new RestTemplate();restTemplate.getMessageConverters().add(new WxMappingJackson2HttpMessageConverter());return restTemplate;
}
我既不推荐把 WxMappingJackson2HttpMessageConverter 实例当作构造 RestTemplate 时的参数来构造 RestTemplate,也不推荐 使用新的 WxMappingJackson2HttpMessageConverter 替换 RestTemplate 默认构造中创建的 MappingJackson2HttpMessageConverter 实例,因为这两种方式都会导致 Content-Type 为 application/json 的 Json 响应没有转换器来反序列化,所以最佳的方式还是“追加”。
RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type..相关推荐
- 使用RestTemplate:报错Could not extract response: no suitable HttpMessageConverter found for response typ
项目中需要调用微信接口获取access_token等一系列和微信接口相关的操作,我使用了Spring自带的RestTemplate类来发送Get或Post请求,直接在Spring配置文件中依赖注入 & ...
- 小小涉及OpenFeign原理:Could not extract response: no suitable HttpMessageConverter found for response type
一.问题解释(想看总结的去最下面) org.springframework.web.client.UnknownContentTypeException: Could not extract resp ...
- Could not extract response: no suitable HttpMessageConverter found for response type [class com.exam
报错信息:Could not extract response: no suitable HttpMessageConverter found for response type [class com ...
- Could not extract response: no suitable HttpMessageConverter found for content type [text/html]
目录 报错信息 源码分析 解决方法 修改 mappingJackson2HttpMessageConverter 配置 继承 mappingJackson2HttpMessageConverter 实 ...
- Could not extract response: no suitable HttpMessageConverter found for response type [class java.lan
解决 Could not extract response: no suitable HttpMessageConverter found for response type [class java. ...
- Could not extract response: no suitable HttpMessageConverter found for response type ***
用RestTemplate.getForObject()获取URL的JSON时,可能会遇到如题所示的报错信息. 我的问题在于:在非MVC的Project中使用RestTemplate. 简单的说,me ...
- openFeig远程调用报错Could not extract response: no suitable HttpMessageConverter found for response type
解决方案:在调用服务和被调用的服务上都加上一个配置类 @Configuration public class jsonConfig {@Beanpublic HttpMessageConverters ...
- restTemplate http请求报错:no suitable HttpMessageConverter found for response type and content type
报错信息: org.springframework.web.client.UnknownContentTypeException: Could not extract response: no sui ...
- Could not extract response: no suitable HttpMessageConverter
版本:spring-cloud-openfeign-core-2.1.1.RELEASE.jar,spring-webmvc-5.1.14.RELEASE.jar,jetty-server-9.4.4 ...
最新文章
- windows2012挂linux盘阵,磁盘阵(IPSAN)挂载Windows和Linux测试过程.doc
- intercontenient hotels
- 拉取数据_Apache Kafka-数据写入过程
- python实现单例模式方法_Python实现单例模式的5种方式
- 程序员,该注意下啦!
- 轻博客:企业品牌互动传播利器
- php实现一个简单的购物网站
- 韦氏评级:担心比特币近期价格走势的人都过于关注短期
- 找出游戏的必胜的策略(博弈论的学习)
- 总结JavaScript中的继承
- 大牛精心挑选的25个Visual Basic学习资料汇总
- stm32g474芯片手册_STM32芯片资料-STM32F4 选型手册.pdf
- 基于EEG的睡眠分期算法记录3-使用决策树多类支持向量机的自动睡眠阶段分类
- FS2116C输入3.7V输出12V2.2A高效升压IC芯片
- 2020-09-21
- Ubuntu 16.04 状态栏实时显示网速、CPU、内存等
- 杨百翰大学 排名Brigham Young University,入学要求,申请条件,简介_施强留学网...
- 《Python基础知识-4判断和循环语句》
- android微信qq分享,android 一键分享 QQ 微信
- 开关电源(Switch Regulator)---Buck