create RestTemplate with LoadBalancerInterceptor using @LoadBalanced qualifier.

fixes https://github.com/spring-cloud/spring-cloud-netflix/issues/267
This commit is contained in:
Spencer Gibb
2015-03-19 16:09:03 -06:00
parent 3002f3c3ea
commit 9e9e30fd70
4 changed files with 224 additions and 4 deletions

View File

@@ -275,4 +275,54 @@ For a Spring Boot Actuator application there are some additional management endp
== Spring Cloud Commons: Common Abstractions
Patterns such as service discovery, load balancing and circuit breakers lend themselves to a common abstraction layer that can be consumed by all Spring Cloud clients, independent of the implementation (e.g. discovery via Eureka or Consul).
Patterns such as service discovery, load balancing and circuit breakers lend themselves to a common abstraction layer that can be consumed by all Spring Cloud clients, independent of the implementation (e.g. discovery via Eureka or Consul).
=== Spring RestTemplate as a Load Balancer Client
You can use Ribbon indirectly via an autoconfigured `RestTemplate`
when RestTemplate is on the classpath and a `LoadBalancerClient` bean is defined):
[source,java,indent=0]
----
public class MyClass {
@Autowired
private RestTemplate restTemplate;
public String doOtherStuff() {
String results = restTemplate.getForObject("http://stores/stores", String.class);
return results;
}
}
----
The URI needs to use a virtual host name (ie. service name, not a host name).
The Ribbon client is used to create a full physical address. See
{github-code}/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java[RibbonAutoConfiguration]
for details of how the `RestTemplate` is set up.
=== Multiple RestTemplate objects
If you want a `RestTemplate` that is not load balanced, create a `RestTemplate`
bean and inject it as normal. To access the load balanced `RestTemplate use
the provided `@LoadBalanced` `Qualifier`:
[source,java,indent=0]
----
public class MyClass {
@Autowired
private RestTemplate restTemplate;
@Autowired
@LoadBalanced
private RestTemplate loadBalanced;
public String doOtherStuff() {
return loadBalanced.getForObject("http://stores/stores", String.class);
}
public String doStuff() {
return restTemplate.getForObject("http://example.com", String.class);
}
}
----

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

View File

@@ -21,7 +21,6 @@ import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
@@ -39,8 +38,8 @@ import org.springframework.web.client.RestTemplate;
public class LoadBalancerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> list = new ArrayList<>();
list.add(loadBalancerInterceptor);

View File

@@ -0,0 +1,133 @@
package org.springframework.cloud.client.loadbalancer;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.SneakyThrows;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
/**
* @author Spencer Gibb
*/
public class LoadBalancerAutoConfigurationTests {
@Test
public void restTemplateGetsLoadBalancerInterceptor() {
ConfigurableApplicationContext context = init(OneRestTemplate.class);
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
assertThat(restTemplates, is(notNullValue()));
assertThat(restTemplates.values(), hasSize(1));
RestTemplate restTemplate = restTemplates.values().iterator().next();
assertThat(restTemplate, is(notNullValue()));
assertLoadBalanced(restTemplate);
}
protected void assertLoadBalanced(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
assertThat(interceptors, hasSize(1));
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
assertThat(interceptor, is(instanceOf(LoadBalancerInterceptor.class)));
}
@Test
public void multipleRestTemplates() {
ConfigurableApplicationContext context = init(TwoRestTemplates.class);
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
assertThat(restTemplates, is(notNullValue()));
Collection<RestTemplate> templates = restTemplates.values();
assertThat(templates, hasSize(2));
TwoRestTemplates.Two two = context.getBean(TwoRestTemplates.Two.class);
assertThat(two.loadBalanced, is(notNullValue()));
assertLoadBalanced(two.loadBalanced);
assertThat(two.nonLoadBalanced, is(notNullValue()));
assertThat(two.nonLoadBalanced.getInterceptors(), is(empty()));
}
protected ConfigurableApplicationContext init(Class<?> config) {
return new SpringApplicationBuilder().web(false).sources(config, LoadBalancerAutoConfiguration.class).run();
}
@Configuration
protected static class OneRestTemplate {
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
}
@Configuration
protected static class TwoRestTemplates {
@Primary
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Configuration
protected static class Two {
@Autowired
RestTemplate nonLoadBalanced;
@Autowired
@LoadBalanced
RestTemplate loadBalanced;
}
}
private static class NoopLoadBalancerClient implements LoadBalancerClient {
private final Random random = new Random();
@Override
public ServiceInstance choose(String serviceId) {
return new DefaultServiceInstance(serviceId, serviceId, random.nextInt(40000), false);
}
@Override
@SneakyThrows
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
return request.apply(choose(serviceId));
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return DefaultServiceInstance.getUri(instance);
}
}
}