Skip to content

Commit ad08ca5

Browse files
author
Dave Syer
committed
Change in contract for @LoadBalanced
Users now need to create their own bean and qualify it, and Spring Cloud will customize it (instead of providing the @bean itself). This is much better for users, since they remain in control of the bean declarations, and can choose which one (if any) is @primary. It's a breaking change for some apps (if they rely on a @LoadBalanced RestTemplate being automatically injected).
1 parent 7eccbc4 commit ad08ca5

File tree

3 files changed

+150
-117
lines changed

3 files changed

+150
-117
lines changed

docs/src/main/asciidoc/spring-cloud-commons.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,21 @@ for details of how the `RestTemplate` is set up.
318318

319319
If you want a `RestTemplate` that is not load balanced, create a `RestTemplate`
320320
bean and inject it as normal. To access the load balanced `RestTemplate use
321-
the provided `@LoadBalanced` `Qualifier`.
321+
the `@LoadBalanced` qualifier when you create your `@Bean`.
322322

323-
IMPORTANT: Notice the `@Primary` annotation on the plain `RestTemplate` declaration.
323+
IMPORTANT: Notice the `@Primary` annotation on the plain `RestTemplate` declaration in the example below, to disambiguate the unqualified `@Autowired` injection.
324324

325325
[source,java,indent=0]
326326
----
327327
@Configuration
328328
public class MyConfiguration {
329329
330+
@LoadBalanced
331+
@Bean
332+
RestTemplate loadBalanced() {
333+
return new RestTemplate();
334+
}
335+
330336
@Primary
331337
@Bean
332338
RestTemplate restTemplate() {

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package org.springframework.cloud.client.loadbalancer;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.List;
2122

23+
import org.springframework.beans.factory.SmartInitializingSingleton;
24+
import org.springframework.beans.factory.annotation.Autowired;
2225
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2326
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2427
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -38,15 +41,23 @@
3841
@ConditionalOnBean(LoadBalancerClient.class)
3942
public class LoadBalancerAutoConfiguration {
4043

41-
@Bean
4244
@LoadBalanced
43-
public RestTemplate loadBalancedRestTemplate(
44-
List<RestTemplateCustomizer> customizers) {
45-
RestTemplate restTemplate = new RestTemplate();
46-
for (RestTemplateCustomizer customizer : customizers) {
47-
customizer.customize(restTemplate);
48-
}
49-
return restTemplate;
45+
@Autowired(required = false)
46+
private List<RestTemplate> restTemplates = Collections.emptyList();
47+
48+
@Bean
49+
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
50+
final List<RestTemplateCustomizer> customizers) {
51+
return new SmartInitializingSingleton() {
52+
@Override
53+
public void afterSingletonsInstantiated() {
54+
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
55+
for (RestTemplateCustomizer customizer : customizers) {
56+
customizer.customize(restTemplate);
57+
}
58+
}
59+
}
60+
};
5061
}
5162

5263
@Bean
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
package org.springframework.cloud.client.loadbalancer;
22

3-
import static org.hamcrest.Matchers.empty;
4-
import static org.hamcrest.Matchers.hasSize;
5-
import static org.hamcrest.Matchers.instanceOf;
6-
import static org.hamcrest.Matchers.is;
7-
import static org.hamcrest.Matchers.notNullValue;
8-
import static org.junit.Assert.assertThat;
9-
103
import java.net.URI;
114
import java.util.Collection;
125
import java.util.List;
136
import java.util.Map;
147
import java.util.Random;
158

16-
import lombok.SneakyThrows;
17-
189
import org.junit.Test;
1910
import org.springframework.beans.factory.annotation.Autowired;
2011
import org.springframework.boot.builder.SpringApplicationBuilder;
@@ -27,107 +18,132 @@
2718
import org.springframework.http.client.ClientHttpRequestInterceptor;
2819
import org.springframework.web.client.RestTemplate;
2920

21+
import static org.hamcrest.Matchers.empty;
22+
import static org.hamcrest.Matchers.hasSize;
23+
import static org.hamcrest.Matchers.instanceOf;
24+
import static org.hamcrest.Matchers.is;
25+
import static org.hamcrest.Matchers.notNullValue;
26+
import static org.junit.Assert.assertThat;
27+
28+
import lombok.SneakyThrows;
29+
3030
/**
3131
* @author Spencer Gibb
3232
*/
3333
public class LoadBalancerAutoConfigurationTests {
3434

35-
@Test
36-
public void restTemplateGetsLoadBalancerInterceptor() {
37-
ConfigurableApplicationContext context = init(OneRestTemplate.class);
38-
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
39-
40-
assertThat(restTemplates, is(notNullValue()));
41-
assertThat(restTemplates.values(), hasSize(1));
42-
RestTemplate restTemplate = restTemplates.values().iterator().next();
43-
assertThat(restTemplate, is(notNullValue()));
44-
45-
assertLoadBalanced(restTemplate);
46-
}
47-
48-
protected void assertLoadBalanced(RestTemplate restTemplate) {
49-
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
50-
assertThat(interceptors, hasSize(1));
51-
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
52-
assertThat(interceptor, is(instanceOf(LoadBalancerInterceptor.class)));
53-
}
54-
55-
@Test
56-
public void multipleRestTemplates() {
57-
ConfigurableApplicationContext context = init(TwoRestTemplates.class);
58-
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
59-
60-
assertThat(restTemplates, is(notNullValue()));
61-
Collection<RestTemplate> templates = restTemplates.values();
62-
assertThat(templates, hasSize(2));
63-
64-
TwoRestTemplates.Two two = context.getBean(TwoRestTemplates.Two.class);
65-
66-
assertThat(two.loadBalanced, is(notNullValue()));
67-
assertLoadBalanced(two.loadBalanced);
68-
69-
assertThat(two.nonLoadBalanced, is(notNullValue()));
70-
assertThat(two.nonLoadBalanced.getInterceptors(), is(empty()));
71-
}
72-
73-
74-
protected ConfigurableApplicationContext init(Class<?> config) {
75-
return new SpringApplicationBuilder().web(false).sources(config, LoadBalancerAutoConfiguration.class).run();
76-
}
77-
78-
@Configuration
79-
protected static class OneRestTemplate {
80-
81-
@Bean
82-
LoadBalancerClient loadBalancerClient() {
83-
return new NoopLoadBalancerClient();
84-
}
85-
86-
}
87-
88-
@Configuration
89-
protected static class TwoRestTemplates {
90-
91-
@Primary
92-
@Bean
93-
RestTemplate restTemplate() {
94-
return new RestTemplate();
95-
}
96-
97-
@Bean
98-
LoadBalancerClient loadBalancerClient() {
99-
return new NoopLoadBalancerClient();
100-
}
101-
102-
@Configuration
103-
protected static class Two {
104-
@Autowired
105-
RestTemplate nonLoadBalanced;
106-
107-
@Autowired
108-
@LoadBalanced
109-
RestTemplate loadBalanced;
110-
}
111-
112-
}
113-
114-
private static class NoopLoadBalancerClient implements LoadBalancerClient {
115-
private final Random random = new Random();
116-
117-
@Override
118-
public ServiceInstance choose(String serviceId) {
119-
return new DefaultServiceInstance(serviceId, serviceId, random.nextInt(40000), false);
120-
}
121-
122-
@Override
123-
@SneakyThrows
124-
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
125-
return request.apply(choose(serviceId));
126-
}
127-
128-
@Override
129-
public URI reconstructURI(ServiceInstance instance, URI original) {
130-
return DefaultServiceInstance.getUri(instance);
131-
}
132-
}
35+
@Test
36+
public void restTemplateGetsLoadBalancerInterceptor() {
37+
ConfigurableApplicationContext context = init(OneRestTemplate.class);
38+
final Map<String, RestTemplate> restTemplates = context
39+
.getBeansOfType(RestTemplate.class);
40+
41+
assertThat(restTemplates, is(notNullValue()));
42+
assertThat(restTemplates.values(), hasSize(1));
43+
RestTemplate restTemplate = restTemplates.values().iterator().next();
44+
assertThat(restTemplate, is(notNullValue()));
45+
46+
assertLoadBalanced(restTemplate);
47+
}
48+
49+
protected void assertLoadBalanced(RestTemplate restTemplate) {
50+
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
51+
assertThat(interceptors, hasSize(1));
52+
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
53+
assertThat(interceptor, is(instanceOf(LoadBalancerInterceptor.class)));
54+
}
55+
56+
@Test
57+
public void multipleRestTemplates() {
58+
ConfigurableApplicationContext context = init(TwoRestTemplates.class);
59+
final Map<String, RestTemplate> restTemplates = context
60+
.getBeansOfType(RestTemplate.class);
61+
62+
assertThat(restTemplates, is(notNullValue()));
63+
Collection<RestTemplate> templates = restTemplates.values();
64+
assertThat(templates, hasSize(2));
65+
66+
TwoRestTemplates.Two two = context.getBean(TwoRestTemplates.Two.class);
67+
68+
assertThat(two.loadBalanced, is(notNullValue()));
69+
assertLoadBalanced(two.loadBalanced);
70+
71+
assertThat(two.nonLoadBalanced, is(notNullValue()));
72+
assertThat(two.nonLoadBalanced.getInterceptors(), is(empty()));
73+
}
74+
75+
protected ConfigurableApplicationContext init(Class<?> config) {
76+
return new SpringApplicationBuilder().web(false)
77+
.properties("spring.aop.proxyTargetClass=true")
78+
.sources(config, LoadBalancerAutoConfiguration.class).run();
79+
}
80+
81+
@Configuration
82+
protected static class OneRestTemplate {
83+
84+
@LoadBalanced
85+
@Bean
86+
RestTemplate loadBalancedRestTemplate() {
87+
return new RestTemplate();
88+
}
89+
90+
@Bean
91+
LoadBalancerClient loadBalancerClient() {
92+
return new NoopLoadBalancerClient();
93+
}
94+
95+
}
96+
97+
@Configuration
98+
protected static class TwoRestTemplates {
99+
100+
@Primary
101+
@Bean
102+
RestTemplate restTemplate() {
103+
return new RestTemplate();
104+
}
105+
106+
@LoadBalanced
107+
@Bean
108+
RestTemplate loadBalancedRestTemplate() {
109+
return new RestTemplate();
110+
}
111+
112+
@Bean
113+
LoadBalancerClient loadBalancerClient() {
114+
return new NoopLoadBalancerClient();
115+
}
116+
117+
@Configuration
118+
protected static class Two {
119+
@Autowired
120+
RestTemplate nonLoadBalanced;
121+
122+
@Autowired
123+
@LoadBalanced
124+
RestTemplate loadBalanced;
125+
}
126+
127+
}
128+
129+
private static class NoopLoadBalancerClient implements LoadBalancerClient {
130+
private final Random random = new Random();
131+
132+
@Override
133+
public ServiceInstance choose(String serviceId) {
134+
return new DefaultServiceInstance(serviceId, serviceId,
135+
this.random.nextInt(40000), false);
136+
}
137+
138+
@Override
139+
@SneakyThrows
140+
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
141+
return request.apply(choose(serviceId));
142+
}
143+
144+
@Override
145+
public URI reconstructURI(ServiceInstance instance, URI original) {
146+
return DefaultServiceInstance.getUri(instance);
147+
}
148+
}
133149
}

0 commit comments

Comments
 (0)