Skip to content

Commit 1cb9b9c

Browse files
bclozeljhoeller
authored andcommitted
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)
1 parent fe4b57c commit 1cb9b9c

File tree

2 files changed

+50
-29
lines changed

2 files changed

+50
-29
lines changed

spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Set;
26+
import javax.xml.transform.Source;
2627

2728
import org.springframework.core.ParameterizedTypeReference;
2829
import org.springframework.http.HttpEntity;
@@ -79,14 +80,14 @@
7980
* {@link #getForObject(String, Class, Map)}), and are capable of substituting any {@linkplain UriTemplate URI templates}
8081
* in that URL using either a {@code String} variable arguments array, or a {@code Map<String, String>}.
8182
* The string varargs variant expands the given template variables in order, so that
82-
* <pre>
83+
* <pre class="code">
8384
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42",
8485
* "21");
8586
* </pre>
8687
* will perform a GET on {@code http://example.com/hotels/42/bookings/21}. The map variant expands the template based
8788
* on variable name, and is therefore more useful when using many variables, or when a single variable is used multiple
8889
* times. For example:
89-
* <pre>
90+
* <pre class="code">
9091
* Map&lt;String, String&gt; vars = Collections.singletonMap("hotel", "42");
9192
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
9293
* </pre>
@@ -95,7 +96,7 @@
9596
* expanded URI multiple times.
9697
*
9798
* <p>Furthermore, the {@code String}-argument methods assume that the URL String is unencoded. This means that
98-
* <pre>
99+
* <pre class="code">
99100
* restTemplate.getForObject("http://example.com/hotel list");
100101
* </pre>
101102
* 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 @@
114115
* requestFactory} and {@link #setErrorHandler(ResponseErrorHandler) errorHandler} bean properties.
115116
*
116117
* @author Arjen Poutsma
118+
* @author Brian Clozel
117119
* @since 3.0
118120
* @see HttpMessageConverter
119121
* @see RequestCallback
@@ -137,21 +139,22 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
137139
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader());
138140

139141

140-
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
141-
142-
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
142+
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
143143

144144
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
145145

146+
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
147+
146148

147149
/**
148150
* Create a new instance of the {@link RestTemplate} using default settings.
151+
* Default {@link HttpMessageConverter}s are initialized.
149152
*/
150153
public RestTemplate() {
151154
this.messageConverters.add(new ByteArrayHttpMessageConverter());
152155
this.messageConverters.add(new StringHttpMessageConverter());
153156
this.messageConverters.add(new ResourceHttpMessageConverter());
154-
this.messageConverters.add(new SourceHttpMessageConverter());
157+
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
155158
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
156159
if (romePresent) {
157160
this.messageConverters.add(new AtomFeedHttpMessageConverter());
@@ -179,14 +182,26 @@ public RestTemplate(ClientHttpRequestFactory requestFactory) {
179182
setRequestFactory(requestFactory);
180183
}
181184

185+
/**
186+
* Create a new instance of the {@link RestTemplate} using the given list of
187+
* {@link HttpMessageConverter} to use
188+
* @param messageConverters the list of {@link HttpMessageConverter} to use
189+
* @since 3.2.7
190+
*/
191+
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
192+
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
193+
this.messageConverters.addAll(messageConverters);
194+
}
195+
182196

183197
/**
184198
* Set the message body converters to use.
185199
* <p>These converters are used to convert from and to HTTP requests and responses.
186200
*/
187201
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
188202
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
189-
this.messageConverters = messageConverters;
203+
this.messageConverters.clear();
204+
this.messageConverters.addAll(messageConverters);
190205
}
191206

192207
/**
@@ -238,6 +253,7 @@ public <T> T getForObject(URI url, Class<T> responseType) throws RestClientExcep
238253

239254
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... urlVariables)
240255
throws RestClientException {
256+
241257
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
242258
ResponseEntityResponseExtractor<T> responseExtractor =
243259
new ResponseEntityResponseExtractor<T>(responseType);
@@ -246,6 +262,7 @@ public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Obj
246262

247263
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables)
248264
throws RestClientException {
265+
249266
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
250267
ResponseEntityResponseExtractor<T> responseExtractor =
251268
new ResponseEntityResponseExtractor<T>(responseType);
@@ -283,6 +300,7 @@ public URI postForLocation(String url, Object request, Object... urlVariables) t
283300

284301
public URI postForLocation(String url, Object request, Map<String, ?> urlVariables)
285302
throws RestClientException {
303+
286304
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
287305
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables);
288306
return headers.getLocation();
@@ -296,6 +314,7 @@ public URI postForLocation(URI url, Object request) throws RestClientException {
296314

297315
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
298316
throws RestClientException {
317+
299318
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
300319
HttpMessageConverterExtractor<T> responseExtractor =
301320
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
@@ -304,6 +323,7 @@ public <T> T postForObject(String url, Object request, Class<T> responseType, Ob
304323

305324
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
306325
throws RestClientException {
326+
307327
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
308328
HttpMessageConverterExtractor<T> responseExtractor =
309329
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
@@ -466,8 +486,9 @@ public <T> T execute(URI url, HttpMethod method, RequestCallback requestCallback
466486
}
467487

468488
/**
469-
* Execute the given method on the provided URI. The {@link ClientHttpRequest} is processed using the {@link
470-
* RequestCallback}; the response with the {@link ResponseExtractor}.
489+
* Execute the given method on the provided URI.
490+
* <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
491+
* the response with the {@link ResponseExtractor}.
471492
* @param url the fully-expanded URL to connect to
472493
* @param method the HTTP method to execute (GET, POST, etc.)
473494
* @param requestCallback object that prepares the request (can be {@code null})
@@ -501,7 +522,7 @@ protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCal
501522
}
502523
catch (IOException ex) {
503524
throw new ResourceAccessException("I/O error on " + method.name() +
504-
" request for \"" + url + "\":" + ex.getMessage(), ex);
525+
" request for \"" + url + "\": " + ex.getMessage(), ex);
505526
}
506527
finally {
507528
if (response != null) {
@@ -513,9 +534,8 @@ protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCal
513534
private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
514535
if (logger.isDebugEnabled()) {
515536
try {
516-
logger.debug(
517-
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
518-
response.getStatusText() + ")");
537+
logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
538+
response.getStatusCode() + " (" + response.getStatusText() + ")");
519539
}
520540
catch (IOException e) {
521541
// ignore
@@ -526,9 +546,8 @@ private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse re
526546
private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
527547
if (logger.isWarnEnabled()) {
528548
try {
529-
logger.warn(
530-
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
531-
response.getStatusText() + "); invoking error handler");
549+
logger.warn(method.name() + " request for \"" + url + "\" resulted in " +
550+
response.getStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
532551
}
533552
catch (IOException e) {
534553
// ignore
@@ -554,7 +573,7 @@ public void doWithRequest(ClientHttpRequest request) throws IOException {
554573
if (responseType != null) {
555574
Class<?> responseClass = null;
556575
if (responseType instanceof Class) {
557-
responseClass = (Class) responseType;
576+
responseClass = (Class<?>) responseType;
558577
}
559578

560579
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
@@ -566,7 +585,7 @@ public void doWithRequest(ClientHttpRequest request) throws IOException {
566585
}
567586
else if (converter instanceof GenericHttpMessageConverter) {
568587

569-
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
588+
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
570589
if (genericConverter.canRead(responseType, null, null)) {
571590
allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
572591
}
@@ -604,7 +623,7 @@ private List<MediaType> getSupportedMediaTypes(HttpMessageConverter<?> messageCo
604623
*/
605624
private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback {
606625

607-
private final HttpEntity requestEntity;
626+
private final HttpEntity<?> requestEntity;
608627

609628
private HttpEntityRequestCallback(Object requestBody) {
610629
this(requestBody, null);
@@ -614,10 +633,10 @@ private HttpEntityRequestCallback(Object requestBody) {
614633
private HttpEntityRequestCallback(Object requestBody, Type responseType) {
615634
super(responseType);
616635
if (requestBody instanceof HttpEntity) {
617-
this.requestEntity = (HttpEntity) requestBody;
636+
this.requestEntity = (HttpEntity<?>) requestBody;
618637
}
619638
else if (requestBody != null) {
620-
this.requestEntity = new HttpEntity(requestBody);
639+
this.requestEntity = new HttpEntity<Object>(requestBody);
621640
}
622641
else {
623642
this.requestEntity = HttpEntity.EMPTY;
@@ -643,7 +662,7 @@ public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
643662
Class<?> requestType = requestBody.getClass();
644663
HttpHeaders requestHeaders = requestEntity.getHeaders();
645664
MediaType requestContentType = requestHeaders.getContentType();
646-
for (HttpMessageConverter messageConverter : getMessageConverters()) {
665+
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
647666
if (messageConverter.canWrite(requestType, requestContentType)) {
648667
if (!requestHeaders.isEmpty()) {
649668
httpRequest.getHeaders().putAll(requestHeaders);
@@ -658,7 +677,8 @@ public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
658677
}
659678

660679
}
661-
messageConverter.write(requestBody, requestContentType, httpRequest);
680+
((HttpMessageConverter<Object>) messageConverter).write(
681+
requestBody, requestContentType, httpRequest);
662682
return;
663683
}
664684
}
@@ -683,7 +703,8 @@ private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<Re
683703
public ResponseEntityResponseExtractor(Type responseType) {
684704
if (responseType != null && !Void.class.equals(responseType)) {
685705
this.delegate = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
686-
} else {
706+
}
707+
else {
687708
this.delegate = null;
688709
}
689710
}

spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -68,9 +68,9 @@ public void setUp() {
6868
response = mock(ClientHttpResponse.class);
6969
errorHandler = mock(ResponseErrorHandler.class);
7070
converter = mock(HttpMessageConverter.class);
71-
template = new RestTemplate(requestFactory);
71+
template = new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(converter));
72+
template.setRequestFactory(requestFactory);
7273
template.setErrorHandler(errorHandler);
73-
template.setMessageConverters(Collections.<HttpMessageConverter<?>>singletonList(converter));
7474
}
7575

7676
@Test

0 commit comments

Comments
 (0)