您现在的位置是:首页 >技术杂谈 >spring RestTemplate自定义HttpMessageConverter网站首页技术杂谈
spring RestTemplate自定义HttpMessageConverter
项目中通过RestTemplate调用其他接口,接口返回的Content-Type是text/json;charset=utf-8,导致调用时报错: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [text/json;charset=utf-8],我们可以通过自定义HttpMessageConverter来解决。
问题分析
我们使用RestTemplate一般会指定返回的数据转换成何种类型,比如如下使用方式,我们告诉RestTemplate将返回的数据转换成A.class的对象。
restTemplate.postForObject("http://localhost:8080/rt/testUrl", entity, A.class);
我们知道,http返回的数据只是一些二进制数据而已,如何将这些二进制数据转换成指定的类型?这就要用到HttpMessageConverter了。跟踪源码我们可以知道,RestTemplate是在org.springframework.web.client.HttpMessageConverterExtractor#extractData这个方法中对数据进行转换的。
为方便后面分析,先把这个方法的代码贴在这里。
public T extractData(ClientHttpResponse response) throws IOException {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
MediaType contentType = getContentType(responseWrapper);
try {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
if (this.responseClass != null) {
if (messageConverter.canRead(this.responseClass, contentType)) {
if (logger.isDebugEnabled()) {
String className = this.responseClass.getName();
logger.debug("Reading to [" + className + "] as "" + contentType + """);
}
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
}
catch (IOException | HttpMessageNotReadableException ex) {
throw new RestClientException("Error while extracting response for type [" +
this.responseType + "] and content type [" + contentType + "]", ex);
}
throw new UnknownContentTypeException(this.responseType, contentType,
responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), getResponseBody(responseWrapper));
}
这里的思路很清楚,先获取response的contentType,然后遍历所有HttpMessageConverter,调用HttpMessageConverter方法的canRead方法判断当前HttpMessageConvert能不能转换这个response,如果可以,就直接调用read方法,并返回结果。从这里也可以看出,HttpMessageConverter的顺序也很重要,一旦发现某个HttpMessageConverter可用,后面的就不会再判断了。
同时注意到,方法会先判断HttpMessageConverter是否是GenericHttpMessageConverter类型的,如果是,就调用canRead方法进行判断是否可以读取当前response。GenericHttpMessageConverter是HttpMessageConverter的子接口,可以支持泛型。我们平时用泛型还是挺多的,所以我们就通过实现GenericHttpMessageConverter来演示如果自定义HttpMessageConverter来支持ContentType为text/json的接口。
问题解决
先把测试代码写一下。
测试接口
@Controller
@RequestMapping("/rt")
public class RtTestController {
@Autowired
private RestTemplate restTemplate;
@ResponseBody
@RequestMapping("test")
public Object test() {
HttpHeaders headers = new HttpHeaders();
HttpEntity entity = new HttpEntity(null, headers);
// 调用testUrl接口,testUrl接口返回的ContentType是text/json
Object o = restTemplate.postForObject("http://localhost:8080/rt/testUrl", entity, Object.class);
return o;
}
@RequestMapping("testUrl")
public ResponseEntity testUrl() {
HttpHeaders headers = new HttpHeaders();
// 指定返回ContentType为text/json
headers.setContentType(MediaType.valueOf("text/json;charset=utf-8"));
// 注意这里返回的数据为{},因为一会我们要用json工具解析数据,所以要返回符合json格式的数据。
ResponseEntity entity = new ResponseEntity("{}", headers, HttpStatus.OK);
return entity;
}
}
配置RestTemplate
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
我们直接调用上面的test接口,就会报错Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [text/json;charset=utf-8],所以我们需要自定义一个HttpMessageConverter,添加到RestTemplate。
通过实现GenericHttpMessageConverter自定义HttpMessageConverter
public class MyCustomGenericHttpMessageConverter implements GenericHttpMessageConverter<Object> {
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
// 如果MediaType是text/json类型,返回true
return MediaType.valueOf("text/json").includes(mediaType);
}
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
// 利用json工具将数据转换成java对象,这里用的是hutool的json工具类。
String content = IoUtil.read(inputMessage.getBody(), Charset.defaultCharset());
return JSONUtil.toBean(content, type, false);
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
// 这里只演示read,所以canWrite返回false.
return false;
}
@Override
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
// GenericHttpMessageConverter用的是canRead(Type type, Class<?> contextClass, MediaType mediaType)
// 所以这个方法可以不用管
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return null;
}
@Override
public Object read(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
}
将自定义的HttpMessageConverter添加到RestTemplate中
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MyCustomGenericHttpMessageConverter());
return restTemplate;
}
}
这样配置好后,再调用test接口就不会报错了。
通过继承AbstractGenericHttpMessageConverter自定义HttpMessageConverter
直接实现GenericHttpMessageConverter我们需要重写很多方法,spring给我们提供了一个更方便的抽象类AbstractGenericHttpMessageConverter,也可以通过继承这个类来自定义,代码如下。
public class MyCustomGenericHttpMessageConverter2 extends AbstractGenericHttpMessageConverter<Object> {
// 通过构造函数指定支持的MediaType
public MyCustomGenericHttpMessageConverter2(MediaType mediaType) {
super(mediaType);
}
@Override
protected void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 这里只演示read
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
// 利用json工具将数据转换成java对象,这里用的是hutool的json工具类。
String content = IoUtil.read(inputMessage.getBody(), Charset.defaultCharset());
return JSONUtil.toBean(content, type, false);
}
}
配置RestTemplate
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MyCustomGenericHttpMessageConverter2 converter = new MyCustomGenericHttpMessageConverter2(MediaType.valueOf("text/json"));
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
}
通过BeanPostProcessor扩展RestTemplate
这里再多说一点吧,由于我们开发使用的是公司的框架,框架中已经封装好了RestTemplate,这时再自定义一个RestTemplate就不合适了,我们可以通过BeanPostProcessor来扩展已有的RestTemplate,代码如下。
@Component
public class RestTemplateBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RestTemplate) {
RestTemplate restBean = (RestTemplate) bean;
MyCustomGenericHttpMessageConverter2 converter = new MyCustomGenericHttpMessageConverter2(MediaType.valueOf("text/json"));
restBean.getMessageConverters().add(converter);
}
return bean;
}
}