您现在的位置是:首页 >技术杂谈 >spring RestTemplate自定义HttpMessageConverter网站首页技术杂谈

spring RestTemplate自定义HttpMessageConverter

gitcat熊 2024-06-17 11:19:32
简介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;
    }
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。