最近看上了个本命域名,因为过期即将被注册局删除,于是就自己写了个api接口用来监控这个域名,等可以注册了就直接发邮件通知我
其中用到了阿里云域名的一个api,准备直接使用RestTemplate去请求这个接口,并将结果自动封装为Map集合方便进行进一步处理。
等到框架搭好,代码写完,以为万事大吉的时候,程序却给我来了个惊喜: 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? 我看阿里云的接口明明返回的是xml的数据啊......仔细再定睛一看,类型还真的是文本类型...
我嘞个去,还有这种操作? 这可怎么办?
当时我没想太多,就直接将返回值保存为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
的数据了
阿里不遵守规范,差评