create RestTemplate with LoadBalancerInterceptor using @LoadBalanced qualifier.
fixes https://github.com/spring-cloud/spring-cloud-netflix/issues/267
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user