From 1cb9b9cf526f5e02202f37ce4d43aee626659c0f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 28 Jan 2014 11:33:45 +0100 Subject: [PATCH] Add RestTemplate constructor with custom converters Prior to this commit, RestTemplate's constructors were all initializing default HTTPMessageConverters. Its API provides a way to replace those converters with custom ones, but default converters are already defined and initialized at that point, which can be an issue in some cases (performance, classpath...). This commits adds a new constructor for RestTemplate with a list of message converters as argument. With this new constructor, default message converters are never initialized. Issue: SPR-11351 (cherry picked from commit 425e5a0) --- .../web/client/RestTemplate.java | 73 ++++++++++++------- .../web/client/RestTemplateTests.java | 6 +- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 8d02c531ed5..2739e386a56 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import javax.xml.transform.Source; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; @@ -79,14 +80,14 @@ import org.springframework.web.util.UriTemplate; * {@link #getForObject(String, Class, Map)}), and are capable of substituting any {@linkplain UriTemplate URI templates} * in that URL using either a {@code String} variable arguments array, or a {@code Map}. * The string varargs variant expands the given template variables in order, so that - *
+ * 
  * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42",
  * "21");
  * 
* will perform a GET on {@code http://example.com/hotels/42/bookings/21}. The map variant expands the template based * on variable name, and is therefore more useful when using many variables, or when a single variable is used multiple * times. For example: - *
+ * 
  * Map<String, String> vars = Collections.singletonMap("hotel", "42");
  * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
  * 
@@ -95,7 +96,7 @@ import org.springframework.web.util.UriTemplate; * expanded URI multiple times. * *

Furthermore, the {@code String}-argument methods assume that the URL String is unencoded. This means that - *

+ * 
  * restTemplate.getForObject("http://example.com/hotel list");
  * 
* will perform a GET on {@code http://example.com/hotel%20list}. As a result, any URL passed that is already encoded @@ -114,6 +115,7 @@ import org.springframework.web.util.UriTemplate; * requestFactory} and {@link #setErrorHandler(ResponseErrorHandler) errorHandler} bean properties. * * @author Arjen Poutsma + * @author Brian Clozel * @since 3.0 * @see HttpMessageConverter * @see RequestCallback @@ -137,21 +139,22 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader()); - private final ResponseExtractor headersExtractor = new HeadersExtractor(); - - private List> messageConverters = new ArrayList>(); + private final List> messageConverters = new ArrayList>(); private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler(); + private final ResponseExtractor headersExtractor = new HeadersExtractor(); + /** * Create a new instance of the {@link RestTemplate} using default settings. + * Default {@link HttpMessageConverter}s are initialized. */ public RestTemplate() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter()); - this.messageConverters.add(new SourceHttpMessageConverter()); + this.messageConverters.add(new SourceHttpMessageConverter()); this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { this.messageConverters.add(new AtomFeedHttpMessageConverter()); @@ -179,6 +182,17 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat setRequestFactory(requestFactory); } + /** + * Create a new instance of the {@link RestTemplate} using the given list of + * {@link HttpMessageConverter} to use + * @param messageConverters the list of {@link HttpMessageConverter} to use + * @since 3.2.7 + */ + public RestTemplate(List> messageConverters) { + Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); + this.messageConverters.addAll(messageConverters); + } + /** * Set the message body converters to use. @@ -186,7 +200,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat */ public void setMessageConverters(List> messageConverters) { Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); - this.messageConverters = messageConverters; + this.messageConverters.clear(); + this.messageConverters.addAll(messageConverters); } /** @@ -238,6 +253,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public ResponseEntity getForEntity(String url, Class responseType, Object... urlVariables) throws RestClientException { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); ResponseEntityResponseExtractor responseExtractor = new ResponseEntityResponseExtractor(responseType); @@ -246,6 +262,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public ResponseEntity getForEntity(String url, Class responseType, Map urlVariables) throws RestClientException { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); ResponseEntityResponseExtractor responseExtractor = new ResponseEntityResponseExtractor(responseType); @@ -283,6 +300,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public URI postForLocation(String url, Object request, Map urlVariables) throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables); return headers.getLocation(); @@ -296,6 +314,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public T postForObject(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters(), logger); @@ -304,6 +323,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public T postForObject(String url, Object request, Class responseType, Map uriVariables) throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters(), logger); @@ -466,8 +486,9 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } /** - * Execute the given method on the provided URI. The {@link ClientHttpRequest} is processed using the {@link - * RequestCallback}; the response with the {@link ResponseExtractor}. + * Execute the given method on the provided URI. + *

The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; + * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) @@ -501,7 +522,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } catch (IOException ex) { throw new ResourceAccessException("I/O error on " + method.name() + - " request for \"" + url + "\":" + ex.getMessage(), ex); + " request for \"" + url + "\": " + ex.getMessage(), ex); } finally { if (response != null) { @@ -513,9 +534,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) { if (logger.isDebugEnabled()) { try { - logger.debug( - method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" + - response.getStatusText() + ")"); + logger.debug(method.name() + " request for \"" + url + "\" resulted in " + + response.getStatusCode() + " (" + response.getStatusText() + ")"); } catch (IOException e) { // ignore @@ -526,9 +546,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException { if (logger.isWarnEnabled()) { try { - logger.warn( - method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" + - response.getStatusText() + "); invoking error handler"); + logger.warn(method.name() + " request for \"" + url + "\" resulted in " + + response.getStatusCode() + " (" + response.getStatusText() + "); invoking error handler"); } catch (IOException e) { // ignore @@ -554,7 +573,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat if (responseType != null) { Class responseClass = null; if (responseType instanceof Class) { - responseClass = (Class) responseType; + responseClass = (Class) responseType; } List allSupportedMediaTypes = new ArrayList(); @@ -566,7 +585,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } else if (converter instanceof GenericHttpMessageConverter) { - GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; + GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; if (genericConverter.canRead(responseType, null, null)) { allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter)); } @@ -604,7 +623,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat */ private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback { - private final HttpEntity requestEntity; + private final HttpEntity requestEntity; private HttpEntityRequestCallback(Object requestBody) { this(requestBody, null); @@ -614,10 +633,10 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat private HttpEntityRequestCallback(Object requestBody, Type responseType) { super(responseType); if (requestBody instanceof HttpEntity) { - this.requestEntity = (HttpEntity) requestBody; + this.requestEntity = (HttpEntity) requestBody; } else if (requestBody != null) { - this.requestEntity = new HttpEntity(requestBody); + this.requestEntity = new HttpEntity(requestBody); } else { this.requestEntity = HttpEntity.EMPTY; @@ -643,7 +662,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat Class requestType = requestBody.getClass(); HttpHeaders requestHeaders = requestEntity.getHeaders(); MediaType requestContentType = requestHeaders.getContentType(); - for (HttpMessageConverter messageConverter : getMessageConverters()) { + for (HttpMessageConverter messageConverter : getMessageConverters()) { if (messageConverter.canWrite(requestType, requestContentType)) { if (!requestHeaders.isEmpty()) { httpRequest.getHeaders().putAll(requestHeaders); @@ -658,7 +677,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } } - messageConverter.write(requestBody, requestContentType, httpRequest); + ((HttpMessageConverter) messageConverter).write( + requestBody, requestContentType, httpRequest); return; } } @@ -683,7 +703,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public ResponseEntityResponseExtractor(Type responseType) { if (responseType != null && !Void.class.equals(responseType)) { this.delegate = new HttpMessageConverterExtractor(responseType, getMessageConverters(), logger); - } else { + } + else { this.delegate = null; } } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index fcfc3407704..63215051978 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,9 +68,9 @@ public class RestTemplateTests { response = mock(ClientHttpResponse.class); errorHandler = mock(ResponseErrorHandler.class); converter = mock(HttpMessageConverter.class); - template = new RestTemplate(requestFactory); + template = new RestTemplate(Collections.>singletonList(converter)); + template.setRequestFactory(requestFactory); template.setErrorHandler(errorHandler); - template.setMessageConverters(Collections.>singletonList(converter)); } @Test