使用 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..相关推荐

  1. 使用RestTemplate:报错Could not extract response: no suitable HttpMessageConverter found for response typ

    项目中需要调用微信接口获取access_token等一系列和微信接口相关的操作,我使用了Spring自带的RestTemplate类来发送Get或Post请求,直接在Spring配置文件中依赖注入 & ...

  2. 小小涉及OpenFeign原理:Could not extract response: no suitable HttpMessageConverter found for response type

    一.问题解释(想看总结的去最下面) org.springframework.web.client.UnknownContentTypeException: Could not extract resp ...

  3. 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 ...

  4. Could not extract response: no suitable HttpMessageConverter found for content type [text/html]

    目录 报错信息 源码分析 解决方法 修改 mappingJackson2HttpMessageConverter 配置 继承 mappingJackson2HttpMessageConverter 实 ...

  5. 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. ...

  6. Could not extract response: no suitable HttpMessageConverter found for response type ***

    用RestTemplate.getForObject()获取URL的JSON时,可能会遇到如题所示的报错信息. 我的问题在于:在非MVC的Project中使用RestTemplate. 简单的说,me ...

  7. openFeig远程调用报错Could not extract response: no suitable HttpMessageConverter found for response type

    解决方案:在调用服务和被调用的服务上都加上一个配置类 @Configuration public class jsonConfig {@Beanpublic HttpMessageConverters ...

  8. restTemplate http请求报错:no suitable HttpMessageConverter found for response type and content type

    报错信息: org.springframework.web.client.UnknownContentTypeException: Could not extract response: no sui ...

  9. 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 ...

最新文章

  1. windows2012挂linux盘阵,磁盘阵(IPSAN)挂载Windows和Linux测试过程.doc
  2. intercontenient hotels
  3. 拉取数据_Apache Kafka-数据写入过程
  4. python实现单例模式方法_Python实现单例模式的5种方式
  5. 程序员,该注意下啦!
  6. 轻博客:企业品牌互动传播利器
  7. php实现一个简单的购物网站
  8. 韦氏评级:担心比特币近期价格走势的人都过于关注短期
  9. 找出游戏的必胜的策略(博弈论的学习)
  10. 总结JavaScript中的继承
  11. 大牛精心挑选的25个Visual Basic学习资料汇总
  12. stm32g474芯片手册_STM32芯片资料-STM32F4 选型手册.pdf
  13. 基于EEG的睡眠分期算法记录3-使用决策树多类支持向量机的自动睡眠阶段分类
  14. FS2116C输入3.7V输出12V2.2A高效升压IC芯片
  15. 2020-09-21
  16. Ubuntu 16.04 状态栏实时显示网速、CPU、内存等
  17. 杨百翰大学 排名Brigham Young University,入学要求,申请条件,简介_施强留学网...
  18. 《Python基础知识-4判断和循环语句》
  19. android微信qq分享,android 一键分享 QQ 微信
  20. 开关电源(Switch Regulator)---Buck

热门文章

  1. rosbag包目标片段截取
  2. 文本文件和二进制文件
  3. 网络安全实验——RC4的实现
  4. c盘空间不足,删除 wer/reportqueue目录文件
  5. 国内的程序员(软件工程师)这一职业,是吃青春饭的吗?
  6. 计算机考研数据结构考试大纲,2017考研大纲:计算机考研大纲文字版之数据结构...
  7. 欧姆龙 PLC CP1E-N30SDR-A 与 NPN型编码器连接
  8. 按回车Enter键后自动隐藏软键盘、进页面自动弹出软键盘
  9. WebSocket入门介绍及编程实战
  10. round( )函数:四舍五入