Spring RestTemplate处理状态为NO_CONTENT的响应时的行为

好的,我有一个名为NamedSystems的类,它的唯一字段是一组NamedSystem。

我有一种方法可以通过某些标准找到NamedSystems。 那不是很重要。 当它得到结果时,一切正常。 但是,当它找不到任何东西,从而返回null(或空 – 我已尝试过两种方式)设置时,我就会遇到问题。 让我解释。

我正在使用Spring RestTemplate类,我在unit testing中进行这样的调用:

ResponseEntity responseEntity = template.exchange(BASE_SERVICE_URL + "? alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}", HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class, alias1.getAlias(), alias1.getAuthority()); 

现在,因为这通常会返回200,但我想返回204,我的服务中有一个拦截器,它确定ModelAndView是否是NamedSystem,如果它的set是null。 如果是,我然后将状态代码设置为NO_CONTENT(204)。

当我运行junit测试时,我收到此错误:

 org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found 

将状态设置为NO_CONTENT似乎擦除了内容类型字段(当我考虑它时确实有意义)。 那为什么还要看呢?

Spring的HttpMessageConverterExtractor extractData方法:

 public T extractData(ClientHttpResponse response) throws IOException { MediaType contentType = response.getHeaders().getContentType(); if (contentType == null) { throw new RestClientException("Cannot extract response: no Content-Type found"); } for (HttpMessageConverter messageConverter : messageConverters) { if (messageConverter.canRead(responseType, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + responseType.getName() + "] as \"" + contentType +"\" using [" + messageConverter + "]"); } return (T) messageConverter.read(this.responseType, response); } } throw new RestClientException( "Could not extract response: no suitable HttpMessageConverter found for response type [" + this.responseType.getName() + "] and content type [" + contentType + "]"); } 

上一点链接找出Extractor的设置位置,我来看看我在测试中使用的RestTemplate的exchange()方法:

 public  ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Object... uriVariables) throws RestClientException { HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType); ResponseEntityResponseExtractor responseExtractor = new ResponseEntityResponseExtractor(responseType); return execute(url, method, requestCallback, responseExtractor, uriVariables); } 

因此,由于交换调用提供的响应类型,它正在尝试将无效转换为空。 如果我将responseType从NamedSystems.class更改为null,它将按预期工作。 它不会尝试转换任何东西。 如果我试图将状态代码设置为404,它也可以正常执行。

我是误入歧途,还是看起来像RestTemplate中的一个缺陷? 当然,我现在正在使用junit所以我知道会发生什么,但如果有人使用RestTemplate来调用它并且不知道服务调用的结果,他们自然会将NamedSystems作为响应类型。 但是,如果他们尝试了没有元素的标准搜索,他们就会遇到这个令人讨厌的错误。

有没有办法解决这个问题而不重写任何RestTemplate的东西? 我是否错误地查看了这种情况? 请帮忙,因为我有点困惑。

我相信您应该查看ResponseExtractor接口并在RestTemplate上调用execute ,提供您对提取器的实现。 对我来说,这似乎是一个常见的要求,所以记录了这个:

https://jira.springsource.org/browse/SPR-8016

这是我之前准备的一个:

 private class MyResponseExtractor extends HttpMessageConverterExtractor { public MyResponseExtractor (Class responseType, List> messageConverters) { super(responseType, messageConverters); } @Override public MyEntity extractData(ClientHttpResponse response) throws IOException { MyEntity result; if (response.getStatusCode() == HttpStatus.OK) { result = super.extractData(response); } else { result = null; } return result; } } 

我已经测试了这个,它似乎做了我想要的。

要创建ResponseExtractor的实例,我调用构造函数并从已注入的RestTemplate实例传递转换器;

例如

 ResponseExtractor responseExtractor = new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters()); 

然后电话是:

 MyEntity responseAsEntity = restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor); 

你的旅费可能会改变。 😉

解决此问题的另一种方法是将响应实体设为null ,如下所示。

  ResponseEntity response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}", HttpMethod.DELETE, requestEntity, null, userID); 

如果仍需要响应头,请尝试实现ResponseErrorHandler。

这是一个简单的解决方案,您可以设置默认的Content-Type,以便在响应中缺少该类型。 Content-Type在返回到预配置的ResponseExtractor以进行提取之前添加到响应标头中。

 public class CustomRestTemplate extends RestTemplate { private MediaType defaultResponseContentType; public CustomRestTemplate() { super(); } public CustomRestTemplate(ClientHttpRequestFactory requestFactory) { super(requestFactory); } public void setDefaultResponseContentType(String defaultResponseContentType) { this.defaultResponseContentType = MediaType.parseMediaType(defaultResponseContentType); } @Override protected  T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor responseExtractor) throws RestClientException { return super.doExecute(url, method, requestCallback, new ResponseExtractor() { public T extractData(ClientHttpResponse response) throws IOException { if (response.getHeaders().getContentType() == null && defaultResponseContentType != null) { response.getHeaders().setContentType(defaultResponseContentType); } return responseExtractor.extractData(response); } }); } } 

现在应在Spring 3.1 RC1中修复此问题。

https://jira.spring.io/browse/SPR-7911

我想你是对的。 我遇到了类似的问题。 我想我们应该得到一个带有NO_CONTENT的HttpStatus和一个空体的ResponseEntity。

或者你可以扩展RestTemplate并覆盖doExecute(..)并检查响应体。

例如,这是我实施并为我们工作的内容:

 @Override protected  T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { final ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); if (!getErrorHandler().hasError(response)) { logResponseStatus(method, url, response); } else { handleResponseError(method, url, response); } if ((response.getBody() == null) || (responseExtractor == null)) { return null; } return responseExtractor.extractData(response); } catch (final IOException ex) { throw new ResourceAccessException("I/O error: " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }