【Wechat】使⽤RestTemplate调⽤获取⽤户信息接⼝解析为信息为json数
据时报错
⽂章⽬录
⼀、背景介绍
使⽤ 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
之所以被摆了⼀道,是因为接⼝⽂档虽说返回的是 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() {
if (romePresent) {
}
if (jackson2XmlPresent) {
}
else if (jaxb2Present) {
}
if (jackson2Present) {
}
else if (gsonPresent) {
}
}
可以看到,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 {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = ute();
handleResponse(url, method, response);
if (responseExtractor != null) {
actData(response);// tag2
}
else {
return null;
}
}
catch (IOException ex) {
String resource = String();
String query = RawQuery();
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/plain
for (HttpMessageConverter<?> messageConverter : ssageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.sponseType, null, contentType)) {// tag4
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + sponseType + "] as \"" +
contentType + "\" using [" + messageConverter + "]");
}
return (T) sponseType, null, responseWrapper);
}
}
if (sponseClass != null) {
if (messageConverter.sponseClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + Name() + "] as \"" +
contentType + "\" using [" + messageConverter + "]");
}
return (T) ad((Class) sponseClass, responseWrapper);
}
}
}
throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
"for response type [" + sponseType + "] and content type [" + contentType + "]");
}
【tag4】处的代码⽤于判断 MappingJackson2HttpMessageConverter 是否⽀持 【tag3】 类型的 MediaType。AbstractJackson2HttpMessageConverter:
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
if (!canRead(mediaType)) {// tag5
return 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, ());
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();
return restTemplate;
}
我既不推荐把 WxMappingJackson2HttpMessageConverter 实例当作构造 RestTemplate 时的参数来构造 RestTemplate,也不推荐使⽤新的 WxMappingJackson2HttpMessageConverter 替换 RestTemplate 默认构造中创建的
MappingJackson2HttpMessageConverter 实例,因为这两种⽅式都会导致 Content-Type 为 application/json 的 Json 响应没有转换器来反序列化,所以最佳的⽅式还是“追加”。
三、第⼆个坑:RestTemplate 的使⽤
其实也不算坑,主要是我太蠢。
⼀开始我是这样写的:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论