Skip to content

Commit 1631ae2

Browse files
committed
Allow RestTemplateBuilder to be further customized
Closes gh-23389
1 parent a7c4116 commit 1631ae2

File tree

5 files changed

+186
-26
lines changed

5 files changed

+186
-26
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.boot.autoconfigure.web.client;
1818

19-
import java.util.Collection;
20-
import java.util.List;
21-
import java.util.function.BiFunction;
2219
import java.util.stream.Collectors;
2320

2421
import org.springframework.beans.factory.ObjectProvider;
@@ -57,26 +54,24 @@ public class RestTemplateAutoConfiguration {
5754
@Bean
5855
@Lazy
5956
@ConditionalOnMissingBean
60-
public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters,
57+
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
58+
ObjectProvider<HttpMessageConverters> messageConverters,
6159
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
6260
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
63-
RestTemplateBuilder builder = new RestTemplateBuilder();
64-
HttpMessageConverters converters = messageConverters.getIfUnique();
65-
if (converters != null) {
66-
builder = builder.messageConverters(converters.getConverters());
67-
}
68-
builder = addCustomizers(builder, restTemplateCustomizers, RestTemplateBuilder::customizers);
69-
builder = addCustomizers(builder, restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
70-
return builder;
61+
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
62+
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
63+
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
64+
configurer.setRestTemplateRequestCustomizers(
65+
restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
66+
return configurer;
7167
}
7268

73-
private <T> RestTemplateBuilder addCustomizers(RestTemplateBuilder builder, ObjectProvider<T> objectProvider,
74-
BiFunction<RestTemplateBuilder, Collection<T>, RestTemplateBuilder> method) {
75-
List<T> customizers = objectProvider.orderedStream().collect(Collectors.toList());
76-
if (!customizers.isEmpty()) {
77-
return method.apply(builder, customizers);
78-
}
79-
return builder;
69+
@Bean
70+
@Lazy
71+
@ConditionalOnMissingBean
72+
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
73+
RestTemplateBuilder builder = new RestTemplateBuilder();
74+
return restTemplateBuilderConfigurer.configure(builder);
8075
}
8176

8277
static class NotReactiveWebApplicationCondition extends NoneNestedConditions {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.client;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.function.BiFunction;
22+
23+
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
24+
import org.springframework.boot.web.client.RestTemplateBuilder;
25+
import org.springframework.boot.web.client.RestTemplateCustomizer;
26+
import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
27+
import org.springframework.util.ObjectUtils;
28+
29+
/**
30+
* Configure {@link RestTemplateBuilder} with sensible defaults.
31+
*
32+
* @author Stephane Nicoll
33+
* @since 2.4.0
34+
*/
35+
public final class RestTemplateBuilderConfigurer {
36+
37+
private HttpMessageConverters httpMessageConverters;
38+
39+
private List<RestTemplateCustomizer> restTemplateCustomizers;
40+
41+
private List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers;
42+
43+
void setHttpMessageConverters(HttpMessageConverters httpMessageConverters) {
44+
this.httpMessageConverters = httpMessageConverters;
45+
}
46+
47+
void setRestTemplateCustomizers(List<RestTemplateCustomizer> restTemplateCustomizers) {
48+
this.restTemplateCustomizers = restTemplateCustomizers;
49+
}
50+
51+
void setRestTemplateRequestCustomizers(List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
52+
this.restTemplateRequestCustomizers = restTemplateRequestCustomizers;
53+
}
54+
55+
/**
56+
* Configure the specified {@link RestTemplateBuilder}. The builder can be further
57+
* tuned and default settings can be overridden.
58+
* @param builder the {@link RestTemplateBuilder} instance to configure
59+
* @return the configured builder
60+
*/
61+
public RestTemplateBuilder configure(RestTemplateBuilder builder) {
62+
if (this.httpMessageConverters != null) {
63+
builder = builder.messageConverters(this.httpMessageConverters.getConverters());
64+
}
65+
builder = addCustomizers(builder, this.restTemplateCustomizers, RestTemplateBuilder::customizers);
66+
builder = addCustomizers(builder, this.restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
67+
return builder;
68+
}
69+
70+
private <T> RestTemplateBuilder addCustomizers(RestTemplateBuilder builder, List<T> customizers,
71+
BiFunction<RestTemplateBuilder, Collection<T>, RestTemplateBuilder> method) {
72+
if (!ObjectUtils.isEmpty(customizers)) {
73+
return method.apply(builder, customizers);
74+
}
75+
return builder;
76+
}
77+
78+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import static org.mockito.BDDMockito.given;
4747
import static org.mockito.Mockito.mock;
4848
import static org.mockito.Mockito.verify;
49+
import static org.mockito.Mockito.verifyNoInteractions;
4950

5051
/**
5152
* Tests for {@link RestTemplateAutoConfiguration}
@@ -58,6 +59,12 @@ class RestTemplateAutoConfigurationTests {
5859
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
5960
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class));
6061

62+
@Test
63+
void restTemplateBuilderConfigurerShouldBeLazilyDefined() {
64+
this.contextRunner.run((context) -> assertThat(
65+
context.getBeanFactory().getBeanDefinition("restTemplateBuilderConfigurer").isLazyInit()).isTrue());
66+
}
67+
6168
@Test
6269
void restTemplateBuilderShouldBeLazilyDefined() {
6370
this.contextRunner.run(
@@ -102,23 +109,39 @@ void restTemplateWhenHasCustomMessageConvertersShouldHaveMessageConverters() {
102109
}
103110

104111
@Test
105-
void restTemplateWhenHasCustomBuilderShouldUseCustomBuilder() {
106-
this.contextRunner.withUserConfiguration(RestTemplateConfig.class, CustomRestTemplateBuilderConfig.class)
112+
void restTemplateShouldApplyCustomizer() {
113+
this.contextRunner.withUserConfiguration(RestTemplateConfig.class, RestTemplateCustomizerConfig.class)
107114
.run((context) -> {
115+
assertThat(context).hasSingleBean(RestTemplate.class);
116+
RestTemplate restTemplate = context.getBean(RestTemplate.class);
117+
RestTemplateCustomizer customizer = context.getBean(RestTemplateCustomizer.class);
118+
verify(customizer).customize(restTemplate);
119+
});
120+
}
121+
122+
@Test
123+
void restTemplateWhenHasCustomBuilderShouldUseCustomBuilder() {
124+
this.contextRunner.withUserConfiguration(RestTemplateConfig.class, CustomRestTemplateBuilderConfig.class,
125+
RestTemplateCustomizerConfig.class).run((context) -> {
108126
assertThat(context).hasSingleBean(RestTemplate.class);
109127
RestTemplate restTemplate = context.getBean(RestTemplate.class);
110128
assertThat(restTemplate.getMessageConverters()).hasSize(1);
111129
assertThat(restTemplate.getMessageConverters().get(0))
112130
.isInstanceOf(CustomHttpMessageConverter.class);
131+
verifyNoInteractions(context.getBean(RestTemplateCustomizer.class));
113132
});
114133
}
115134

116135
@Test
117-
void restTemplateShouldApplyCustomizer() {
118-
this.contextRunner.withUserConfiguration(RestTemplateConfig.class, RestTemplateCustomizerConfig.class)
136+
void restTemplateWhenHasCustomBuilderCouldReuseBuilderConfigurer() {
137+
this.contextRunner.withUserConfiguration(RestTemplateConfig.class,
138+
CustomRestTemplateBuilderWithConfigurerConfig.class, RestTemplateCustomizerConfig.class)
119139
.run((context) -> {
120140
assertThat(context).hasSingleBean(RestTemplate.class);
121141
RestTemplate restTemplate = context.getBean(RestTemplate.class);
142+
assertThat(restTemplate.getMessageConverters()).hasSize(1);
143+
assertThat(restTemplate.getMessageConverters().get(0))
144+
.isInstanceOf(CustomHttpMessageConverter.class);
122145
RestTemplateCustomizer customizer = context.getBean(RestTemplateCustomizer.class);
123146
verify(customizer).customize(restTemplate);
124147
});
@@ -147,14 +170,16 @@ void builderShouldBeFreshForEachUse() {
147170
@Test
148171
void whenServletWebApplicationRestTemplateBuilderIsConfigured() {
149172
new WebApplicationContextRunner().withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class))
150-
.run((context) -> assertThat(context).hasSingleBean(RestTemplateBuilder.class));
173+
.run((context) -> assertThat(context).hasSingleBean(RestTemplateBuilder.class)
174+
.hasSingleBean(RestTemplateBuilderConfigurer.class));
151175
}
152176

153177
@Test
154178
void whenReactiveWebApplicationRestTemplateBuilderIsNotConfigured() {
155179
new ReactiveWebApplicationContextRunner()
156180
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class))
157-
.run((context) -> assertThat(context).doesNotHaveBean(RestTemplateBuilder.class));
181+
.run((context) -> assertThat(context).doesNotHaveBean(RestTemplateBuilder.class)
182+
.doesNotHaveBean(RestTemplateBuilderConfigurer.class));
158183
}
159184

160185
@Configuration(proxyBeanMethods = false)
@@ -208,6 +233,16 @@ RestTemplateBuilder restTemplateBuilder() {
208233

209234
}
210235

236+
@Configuration(proxyBeanMethods = false)
237+
static class CustomRestTemplateBuilderWithConfigurerConfig {
238+
239+
@Bean
240+
RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
241+
return configurer.configure(new RestTemplateBuilder()).messageConverters(new CustomHttpMessageConverter());
242+
}
243+
244+
}
245+
211246
@Configuration(proxyBeanMethods = false)
212247
static class RestTemplateCustomizerConfig {
213248

spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5975,7 +5975,16 @@ The following example shows a customizer that configures the use of a proxy for
59755975
include::{code-examples}/web/client/RestTemplateProxyCustomizationExample.java[tag=customizer]
59765976
----
59775977

5978-
Finally, the most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean.
5978+
Finally, you can also create your own `RestTemplateBuilder` bean.
5979+
To prevent switching off the auto-configuration of a `RestTemplateBuilder` and prevent any `RestTemplateCustomizer` beans from being used, make sure to configure your custom instance with a `RestTemplateBuilderConfigurer`.
5980+
The following example exposes a `RestTemplateBuilder` with what Spring Boot would auto-configure, except that custom connect and read timeouts are also specified:
5981+
5982+
[source,java,indent=0]
5983+
----
5984+
include::{code-examples}/web/client/RestTemplateBuilderCustomizationExample.java[tag=customizer]
5985+
----
5986+
5987+
The most extreme (and rarely used) option is to create your own `RestTemplateBuilder` bean without using a configurer.
59795988
Doing so switches off the auto-configuration of a `RestTemplateBuilder` and prevents any `RestTemplateCustomizer` beans from being used.
59805989

59815990

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.web.client;
18+
19+
import java.time.Duration;
20+
21+
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
22+
import org.springframework.boot.web.client.RestTemplateBuilder;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
26+
/**
27+
* Example configuration for using a {@link RestTemplateBuilderConfigurer} to configure a
28+
* custom {@link RestTemplateBuilder}.
29+
*
30+
* @author Stephane Nicoll
31+
*/
32+
@Configuration(proxyBeanMethods = false)
33+
public class RestTemplateBuilderCustomizationExample {
34+
35+
// tag::customizer[]
36+
@Bean
37+
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
38+
return configurer.configure(new RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
39+
.setReadTimeout(Duration.ofSeconds(2));
40+
}
41+
// end::customizer[]
42+
43+
}

0 commit comments

Comments
 (0)