RestTemplate调用微信小程接口异常:RestClientException: Could not extract response: no suitable HttpMessageConve

第一次玩微信小程序,调用第一个接口jscode2session居然就出问题了。采用RestTemplate访问https://api.weixin.qq.com/sns/jscode2session,就提示异常:

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的消息转换器集合中默认是没有这个类型的,所以需要把这个类型加入到消息转换器中。

分析:

public RestTemplate() {
    this.messageConverters = new ArrayList();
    this.errorHandler = new DefaultResponseErrorHandler();
    this.headersExtractor = new RestTemplate.HeadersExtractor();
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter(false));
    if (!shouldIgnoreXml) {
        try {
            this.messageConverters.add(new SourceHttpMessageConverter());
        } catch (Error var2) {
        }
    }

    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    if (romePresent) {
        this.messageConverters.add(new AtomFeedHttpMessageConverter());
        this.messageConverters.add(new RssChannelHttpMessageConverter());
    }

    if (!shouldIgnoreXml) {
        if (jackson2XmlPresent) {
            this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
        } else if (jaxb2Present) {
            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }
    }

    if (jackson2Present) {
        this.messageConverters.add(new MappingJackson2HttpMessageConverter());
    } else if (gsonPresent) {
        this.messageConverters.add(new GsonHttpMessageConverter());
    } else if (jsonbPresent) {
        this.messageConverters.add(new JsonbHttpMessageConverter());
    } else if (kotlinSerializationJsonPresent) {
        this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
    }

    if (jackson2SmilePresent) {
        this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
    }

    if (jackson2CborPresent) {
        this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
    }

    this.uriTemplateHandler = initUriTemplateHandler();
}

而springboot内置的就是jackson来进行json的序列化和反序列号,通过debug也可以验证这一点(jackson2Present == true),也就是说RestTemplate会默认加入MappingJackson2HttpMessageConverter这个转换器,它有两个构造方法,我们可以发现其支持的MediaType为application/json

public MappingJackson2HttpMessageConverter() {
    this(Jackson2ObjectMapperBuilder.json().build()); // 这里调用的是第二个构造方法
}

public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
    super(objectMapper, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});
}

通过debug,发现RestTemplate会执行doExecute方法

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    Assert.notNull(url, "URI is required");
    Assert.notNull(method, "HttpMethod is required");
    ClientHttpResponse response = null;

    Object var14;
    try {
        ClientHttpRequest request = this.createRequest(url, method);
        if (requestCallback != null) {
            requestCallback.doWithRequest(request);
        }

        response = request.execute();
        this.handleResponse(url, method, response);
        var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
    } catch (IOException var12) {
        String resource = url.toString();
        String query = url.getRawQuery();
        resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
        throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
    } finally {
        if (response != null) {
            response.close();
        }

    }

    return var14;
}

responseExtractor.extractData这个方法负责从httpresponse中获取数据,利用到了消息转换器。实际进入到类HttpMessageConverterExtractor中:

public T extractData(ClientHttpResponse response) throws IOException {
    MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
    if (responseWrapper.hasMessageBody() && !responseWrapper.hasEmptyMessageBody()) {
        MediaType contentType = this.getContentType(responseWrapper);

        try {
            Iterator var4 = this.messageConverters.iterator();

            while(var4.hasNext()) {
                HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var4.next();
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter)messageConverter;
//此处即是判断消息转换器的类型是否会http响应的类型相同
                    if (genericMessageConverter.canRead(this.responseType, (Class)null, contentType)) {
                        if (this.logger.isDebugEnabled()) {
                            ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                            this.logger.debug("Reading to [" + resolvableType + "]");
                        }

                        return genericMessageConverter.read(this.responseType, (Class)null, responseWrapper);
                    }
                }

                if (this.responseClass != null && messageConverter.canRead(this.responseClass, contentType)) {
                    if (this.logger.isDebugEnabled()) {
                        String className = this.responseClass.getName();
                        this.logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
                    }

                    return messageConverter.read(this.responseClass, responseWrapper);
                }
            }
        } catch (HttpMessageNotReadableException | IOException var8) {
            throw new RestClientException("Error while extracting response for type [" + this.responseType + "] and content type [" + contentType + "]", var8);
        }

        throw new UnknownContentTypeException(this.responseType, contentType, responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(), responseWrapper.getHeaders(), getResponseBody(responseWrapper));
    } else {
        return null;
    }
}

通过红色标记的异常,正是我们程序输出的异常。不难发现,这个方法一开始就会获取所有的消息转换器,然后逐条遍历,判断是否有相应的转换器的类型与http响应类型responseType相同,如果都没有相同的,则会抛出该异常。到这里我们就会容易想到,既然原因是没有这个类型text/plain引起的,我们可以在转换器集合中添加这个类型。

两种解决方法:

1、我们可以仿造MappingJackson2HttpMessageConverter类,创建一个MyMappingJackson2HttpMessageConverter类,将里面的

super(objectMapper, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});

改为

super(objectMapper, new MediaType[]{MediaType.TEXT_PLAIN, new MediaType("text", "*+plain")});

2.也可以直接继承MappingJackson2HttpMessageConverter类,自定义MyMappingJackson2HttpMessageConverter类,在其构造方法中添加Text_PLAIN类型

public class MyMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public WxMappingJackson2HttpMessageConverter(){
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.TEXT_PLAIN);
        setSupportedMediaTypes(mediaTypes);
    }
}

最后在我们的springboot项目中构建RestTemplate对象的地方,添加我们自定义的消息转换器

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        RestTemplate restTemplate = builder.build();
        restTemplate.getMessageConverters().add(new WxMappingJackson2HttpMessageConverter());
        return restTemplate;
    }
}

到此,分析完毕。

借鉴了下面博主的这个分析,自己也重新做了分析验证和其它的解决方法,不过殊途同归,有不正确的地方还请大佬不吝指教。RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type.._z69183787的专栏-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余额很不足

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值