最近看上了个本命域名,因为过期即将被注册局删除,于是就自己写了个api接口用来监控这个域名,等可以注册了就直接发邮件通知我 吐舌.png

其中用到了阿里云域名的一个api,准备直接使用RestTemplate去请求这个接口,并将结果自动封装为Map集合方便进行进一步处理。

等到框架搭好,代码写完,以为万事大吉的时候,程序却给我来了个惊喜: 疑问.png
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/plain;charset=GBK]

很明显,上面提示接口返回的是text/plain文本类型数据,RestTemplate解析数据的时候没有找到合适的解析器,就抛了异常。
WTF? what.png 我看阿里云的接口明明返回的是xml的数据啊......仔细再定睛一看,类型还真的是文本类型...

2019-12-06T13:13:18.png

我嘞个去,还有这种操作? 黑线.png 这可怎么办?
当时我没想太多,就直接将返回值保存为String类型,然后使用jackson直接解析成对象了,这样也能进行数据处理了;

不过问题虽然暂时解决了,可终究不是好办法,RestTemplate本身就内置了jackson解析器,所以说肯定能直接让RestTemplate解析这个数据的;

本着极客精神,今天有空就看了下源码:

根据程序提示的异常信息:

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/plain;charset=GBK]

    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:123)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1001)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:984)

先进入HttpMessageConverterExtractor.java中,于是看到如下逻辑

HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var4.next();
if (messageConverter instanceof GenericHttpMessageConverter) {
    GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter)messageConverter;
    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);
}

可以看到messageConverter.canRead(this.responseClass, contentType)方法是根据contentType来判断这个MessageConverter能不能处理响应的内容,由于我需要处理的类型为xml,所以就进入MappingJackson2XmlHttpMessageConverter类,并终于在它的爸爸类AbstractJackson2HttpMessageConverter的爷爷类AbstractGenericHttpMessageConverter的祖宗类AbstractHttpMessageConverter中的canRead方法中找到了对应的处理逻辑:

protected boolean canRead(@Nullable MediaType mediaType) {
    if (mediaType == null) {
        return true;
    } else {
        Iterator var2 = this.getSupportedMediaTypes().iterator();

        MediaType supportedMediaType;
        do {
            if (!var2.hasNext()) {
                return false;
            }

            supportedMediaType = (MediaType)var2.next();
        } while(!supportedMediaType.includes(mediaType));

        return true;
    }
}

这里可以看到canRead方法就是根据内置的mediaType列表来判断是否能够解析传入的contentType类型,所以解决方法就是将text/plain文本类数据加入到xml解析器的mediaType列表中,让xml解析器默认支持解析text/plain类型,可以直接在创建RestTemplate的时候实现,具体代码如下:

@Bean
public RestTemplate restTemplate() {
    //创建RestTemplate对象,并且使用okHttp客户端
    RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    //先获取到converter列表
    List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
    for(HttpMessageConverter<?> converter : converters){
        //让jsonConverter支持对text/plain的解析
        if(converter instanceof MappingJackson2XmlHttpMessageConverter){
            try{
                //先将原先支持的MediaType列表拷出
                List<MediaType> mediaTypeList = new ArrayList<>(converter.getSupportedMediaTypes());
                //加入对text/plain的支持
                mediaTypeList.add(MediaType.TEXT_PLAIN);
                //将已经加入了text/plain的MediaType支持列表设置为其支持的媒体类型列表
                ((MappingJackson2XmlHttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    return restTemplate;
}

至此,我的RestTemplate就能处理返回的是xml数据,不过类型为text/plain的数据了 太开心.png