Skip to content

Commit b382310

Browse files
committed
Provide web client Auto-configuration
Add auto-configuration for use with typical web clients. Currently client auto-configuration consists of a RestTemplateBuilder pre-configured with HttpMessageConverters. Closes spring-projectsgh-5507
1 parent b641e63 commit b382310

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2016 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+
* http://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;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
24+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.boot.web.client.RestTemplateBuilder;
28+
import org.springframework.boot.web.client.RestTemplateCustomizer;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
32+
import org.springframework.util.CollectionUtils;
33+
import org.springframework.web.client.RestTemplate;
34+
35+
/**
36+
* {@link EnableAutoConfiguration Auto-configuration} for web client.
37+
*
38+
* @author Stephane Nicoll
39+
* @author Phillip Webb
40+
* @since 1.4.0
41+
*/
42+
@Configuration
43+
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
44+
public class WebClientAutoConfiguration {
45+
46+
@Configuration
47+
@ConditionalOnClass(RestTemplate.class)
48+
public static class RestTemplateConfiguration {
49+
50+
private final ObjectProvider<HttpMessageConverters> messageConverters;
51+
52+
private final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers;
53+
54+
public RestTemplateConfiguration(
55+
ObjectProvider<HttpMessageConverters> messageConverters,
56+
ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
57+
this.messageConverters = messageConverters;
58+
this.restTemplateCustomizers = restTemplateCustomizers;
59+
}
60+
61+
@Bean
62+
@ConditionalOnMissingBean
63+
public RestTemplateBuilder restTemplateBuilder() {
64+
RestTemplateBuilder builder = new RestTemplateBuilder();
65+
HttpMessageConverters converters = this.messageConverters.getIfUnique();
66+
if (converters != null) {
67+
builder = builder.messageConverters(converters.getConverters());
68+
}
69+
List<RestTemplateCustomizer> customizers = this.restTemplateCustomizers
70+
.getIfAvailable();
71+
if (!CollectionUtils.isEmpty(customizers)) {
72+
customizers = new ArrayList<RestTemplateCustomizer>(customizers);
73+
AnnotationAwareOrderComparator.sort(customizers);
74+
builder = builder.customizers(customizers);
75+
}
76+
return builder;
77+
}
78+
79+
}
80+
81+
}

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
9494
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
9595
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
9696
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
97+
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
9798
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
9899
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
99100
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright 2012-2016 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+
* http://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;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.junit.After;
23+
import org.junit.Test;
24+
25+
import org.springframework.boot.web.client.RestTemplateBuilder;
26+
import org.springframework.boot.web.client.RestTemplateCustomizer;
27+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
31+
import org.springframework.http.converter.HttpMessageConverter;
32+
import org.springframework.http.converter.StringHttpMessageConverter;
33+
import org.springframework.web.client.RestTemplate;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.mock;
37+
import static org.mockito.Mockito.verify;
38+
39+
/**
40+
* Tests for {@link WebClientAutoConfiguration}
41+
*
42+
* @author Stephane Nicoll
43+
* @author Phillip Webb
44+
*/
45+
public class WebClientAutoConfigurationTests {
46+
47+
private AnnotationConfigApplicationContext context;
48+
49+
@After
50+
public void close() {
51+
if (this.context != null) {
52+
this.context.close();
53+
}
54+
}
55+
56+
@Test
57+
public void restTemplateWhenMessageConvertersDefinedShouldHaveMessageConverters() {
58+
load(HttpMessageConvertersAutoConfiguration.class, RestTemplateConfig.class);
59+
assertThat(this.context.getBeansOfType(RestTemplate.class)).hasSize(1);
60+
RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
61+
List<HttpMessageConverter<?>> converters = this.context
62+
.getBean(HttpMessageConverters.class).getConverters();
63+
assertThat(restTemplate.getMessageConverters())
64+
.containsExactlyElementsOf(converters);
65+
assertThat(restTemplate.getRequestFactory())
66+
.isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
67+
}
68+
69+
@Test
70+
public void restTemplateWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() {
71+
load(RestTemplateConfig.class);
72+
RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
73+
assertThat(restTemplate.getMessageConverters().size())
74+
.isEqualTo(new RestTemplate().getMessageConverters().size());
75+
}
76+
77+
@Test
78+
public void restTemplateWhenHasCustomMesssageConvertersShouldHaveMessageConverters() {
79+
load(CustomHttpMessageConverter.class,
80+
HttpMessageConvertersAutoConfiguration.class, RestTemplateConfig.class);
81+
RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
82+
List<Class<?>> converterClasses = new ArrayList<Class<?>>();
83+
for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
84+
converterClasses.add(converter.getClass());
85+
}
86+
assertThat(converterClasses).contains(CustomHttpMessageConverter.class);
87+
}
88+
89+
@Test
90+
public void restTemplateWhenHasCustomBuilderShouldUseCustomBuilder() {
91+
load(RestTemplateConfig.class, CustomRestTemplateBuilderConfig.class);
92+
assertThat(this.context.getBeansOfType(RestTemplate.class)).hasSize(1);
93+
RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
94+
assertThat(restTemplate.getMessageConverters()).hasSize(1);
95+
assertThat(restTemplate.getMessageConverters().get(0))
96+
.isInstanceOf(CustomHttpMessageConverter.class);
97+
}
98+
99+
@Test
100+
public void restTemplateShouldApplyCustomizer() throws Exception {
101+
load(RestTemplateCustomizerConfig.class, RestTemplateConfig.class);
102+
RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
103+
RestTemplateCustomizer customizer = this.context
104+
.getBean(RestTemplateCustomizer.class);
105+
verify(customizer).customize(restTemplate);
106+
}
107+
108+
@Test
109+
public void builderShouldBeFreshForEachUse() throws Exception {
110+
load(DirtyRestTemplateConfig.class);
111+
}
112+
113+
public void load(Class<?>... config) {
114+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
115+
ctx.register(config);
116+
ctx.register(WebClientAutoConfiguration.class);
117+
ctx.refresh();
118+
this.context = ctx;
119+
}
120+
121+
@Configuration
122+
static class RestTemplateConfig {
123+
124+
@Bean
125+
public RestTemplate restTemplate(RestTemplateBuilder builder) {
126+
return builder.build();
127+
}
128+
129+
}
130+
131+
@Configuration
132+
static class DirtyRestTemplateConfig {
133+
134+
@Bean
135+
public RestTemplate restTemplateOne(RestTemplateBuilder builder) {
136+
try {
137+
return builder.build();
138+
}
139+
finally {
140+
breakBuilderOnNextCall(builder);
141+
}
142+
}
143+
144+
@Bean
145+
public RestTemplate restTemplateTwo(RestTemplateBuilder builder) {
146+
try {
147+
return builder.build();
148+
}
149+
finally {
150+
breakBuilderOnNextCall(builder);
151+
}
152+
}
153+
154+
private void breakBuilderOnNextCall(RestTemplateBuilder builder) {
155+
builder.additionalCustomizers(new RestTemplateCustomizer() {
156+
157+
@Override
158+
public void customize(RestTemplate restTemplate) {
159+
throw new IllegalStateException();
160+
}
161+
162+
});
163+
}
164+
165+
}
166+
167+
@Configuration
168+
static class CustomRestTemplateBuilderConfig {
169+
170+
@Bean
171+
public RestTemplateBuilder restTemplateBuilder() {
172+
return new RestTemplateBuilder()
173+
.messageConverters(new CustomHttpMessageConverter());
174+
}
175+
176+
}
177+
178+
@Configuration
179+
static class RestTemplateCustomizerConfig {
180+
181+
@Bean
182+
public RestTemplateCustomizer restTemplateCustomizer() {
183+
return mock(RestTemplateCustomizer.class);
184+
}
185+
186+
}
187+
188+
static class CustomHttpMessageConverter extends StringHttpMessageConverter {
189+
190+
}
191+
192+
}

0 commit comments

Comments
 (0)