好吧,我有一个 NamedSystems 类,它的唯一字段是一组 NamedSystem。
我有一种方法可以根据某些标准查找命名系统。这并不重要。当得到结果时,一切都会正常进行。然而,当它找不到任何东西,从而返回一个空(或空——我已经尝试了两种方法)集时,我就会遇到问题。让我解释一下。
我正在使用 Spring RestTemplate 类,并且我在单元测试中进行如下调用:
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 以及它的集合是否为 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 <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
因此,它试图将由于交换调用提供的响应类型而导致的内容转换为无。如果我将responseType从NamedSystems.class更改为null,它会按预期工作。它不会尝试转换任何内容。如果我尝试将状态代码设置为 404,它也可以正常执行。
我是否被误导了,或者这看起来像是 RestTemplate 中的一个缺陷?当然,我现在使用的是 junit,所以我知道会发生什么,但是如果有人使用 RestTemplate 来调用它并且不知道服务调用的结果,他们自然会使用 NamedSystems 作为响应类型。然而,如果他们尝试进行没有元素的标准搜索,他们就会遇到这个令人讨厌的错误。
有没有办法解决这个问题而不覆盖任何 RestTemplate 的东西?我对这种情况的看法是否错误?请帮忙,因为我有点困惑。
解决此问题的另一种方法是将响应实体设为 null,如下所示。
ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}", HttpMethod.DELETE, requestEntity, null, userID);
如果您仍然需要响应标头,请尝试实现 ResponseErrorHandler。
我相信您可能应该查看 ResponseExtractor 接口并在提供提取器实现的 RestTemplate 上调用 execute。 对我来说,这似乎是执行此操作的常见要求,因此已记录此内容:
https://jira.springsource.org/browse/SPR-8016
这是我之前准备的:
private class MyResponseExtractor extends HttpMessageConverterExtractor<MyEntity> {
public MyResponseExtractor (Class<MyEntity> responseType,
List<HttpMessageConverter<?>> 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<MyEntity> responseExtractor =
new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());
那么调用的是:
MyEntity responseAsEntity =
restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);
您的里程可能会有所不同。 ;-)
这是一个简单的解决方案,您可以设置默认的 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> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
throws RestClientException {
return super.doExecute(url, method, requestCallback, new ResponseExtractor<T>() {
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://github.com/spring-projects/spring-framework/issues/12566(以前的SPR-7911)
或者您可以扩展 RestTemplate 并覆盖 doExecute(..) 并检查响应正文。
例如,这是我实施并为我们工作的:
@Override
protected <T> T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor<T> 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();
}
}
}
我认为你是对的。 我也有类似的问题。 我认为我们应该得到一个 HttpStatus 为 NO_CONTENT 且主体为 null 的 ResponseEntity。
我找到了一个解决方法(不确定它是否符合您的情况):
首先定义一个实现ClientHttpRequestInterceptor的自定义拦截器类。并检查response.getStatusCode()是否满足您的情况(我的情况是!= HttpStatus.NOT_FOUND并且response.getBody()长度为0),定义一个自定义类(例如DefaultResponseForEmptyRestTemplateBody),它具有类型为MockClientHttpResponse的静态方法:
public class RequestResponseInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if(response.getStatusCode()!=HttpStatus.NOT_FOUND && response.getBody().readAllBytes().length==0){
response = DefaultResponseForEmptyRestTemplateBody.getResponse(response.getStatusCode());
}
return response;
}
}
public static class DefaultResponseForEmptyRestTemplateBody {
MockClientHttpResponse response;
private static byte[] content = new byte[0];
public static MockClientHttpResponse getResponse(HttpStatus statusCode){
content = "response body is empty".getBytes();
return new MockClientHttpResponse(content, statusCode);
}
}
最后将此拦截器添加到您的restTemplate对象中,如下所示:
restTemplate.setInterceptors(Collections.singletonList(new RequestResponseLoggingInterceptor()));
并调用你的restTemplate.postForEntity:
ResponseEntity<String> response = this.restTemplate.postForEntity(baseUrl, requestParams,String.class);