Making spring-retry optional by conditional loading config

This commit is contained in:
Ryan Baxter
2016-10-20 14:36:39 -04:00
parent 19a7562c7c
commit 39045ee20e
10 changed files with 620 additions and 248 deletions

View File

@@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -63,38 +65,66 @@ public class LoadBalancerAutoConfiguration {
};
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient) {
return new LoadBalancerInterceptor(loadBalancerClient);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
static class RetryAutoConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties, lbRetryPolicyFactory);
@Bean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties, lbRetryPolicyFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}

View File

@@ -16,19 +16,13 @@
package org.springframework.cloud.client.loadbalancer;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import java.io.IOException;
import java.net.URI;
/**
* @author Spencer Gibb
@@ -38,76 +32,26 @@ import java.net.URI;
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private RetryTemplate retryTemplate;
private LoadBalancerRetryProperties lbProperties;
private LoadBalancedRetryPolicyFactory lbRetryPolicyFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate, LoadBalancerRetryProperties lbProperties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) {
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
this.retryTemplate = retryTemplate;
this.lbProperties = lbProperties;
this.lbRetryPolicyFactory = lbRetryPolicyFactory;
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName,
loadBalancer);
retryTemplate.setRetryPolicy(
!lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer,
serviceName));
return retryTemplate
.execute(new RetryCallback<ClientHttpResponse, IOException>() {
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse doWithRetry(RetryContext context)
throws IOException {
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = loadBalancer.choose(serviceName);
}
return LoadBalancerInterceptor.this.loadBalancer.execute(
serviceName, serviceInstance,
new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(
final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(
request, instance);
return execution.execute(serviceRequest, body);
}
});
public ClientHttpResponse apply(final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request,
instance, loadBalancer);
return execution.execute(serviceRequest, body);
}
});
}
private class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance) {
super(request);
this.instance = instance;
}
@Override
public URI getURI() {
URI uri = LoadBalancerInterceptor.this.loadBalancer.reconstructURI(
this.instance, getRequest().getURI());
return uri;
}
}
}

View File

@@ -0,0 +1,77 @@
package org.springframework.cloud.client.loadbalancer;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* @author Ryan Baxter
*/
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancedRetryPolicyFactory lbRetryPolicyFactory;
private RetryTemplate retryTemplate;
private LoadBalancerClient loadBalancer;
private LoadBalancerRetryProperties lbProperties;
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate,
LoadBalancerRetryProperties lbProperties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) {
this.loadBalancer = loadBalancer;
this.lbRetryPolicyFactory = lbRetryPolicyFactory;
this.retryTemplate = retryTemplate;
this.lbProperties = lbProperties;
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName,
loadBalancer);
retryTemplate.setRetryPolicy(
!lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer,
serviceName));
return retryTemplate
.execute(new RetryCallback<ClientHttpResponse, IOException>() {
@Override
public ClientHttpResponse doWithRetry(RetryContext context)
throws IOException {
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = loadBalancer.choose(serviceName);
}
return RetryLoadBalancerInterceptor.this.loadBalancer.execute(
serviceName, serviceInstance,
new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(
final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(
request, instance, loadBalancer);
return execution.execute(serviceRequest, body);
}
});
}
});
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.cloud.client.loadbalancer;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.support.HttpRequestWrapper;
/**
* @author Ryan Baxter
*/
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,
LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(
this.instance, getRequest().getURI());
return uri;
}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Taken from Spring Boot test utils.
* https://github.com/spring-projects/spring-boot/blob/1.4.x/spring-boot/src/test/java/org/springframework/boot/testutil/ClassPathExclusions.java
* @author Ryan Baxter
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassPathExclusions {
/**
* One or more Ant-style patterns that identify entries to be excluded from the class
* path. Matching is performed against an entry's {@link File#getName() file name}.
* For example, to exclude Hibernate Validator from the classpath,
* {@code "hibernate-validator-*.jar"} can be used.
* @return the exclusion patterns
*/
String[] value();
}

View File

@@ -0,0 +1,220 @@
package org.springframework.cloud;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
/**
* Taken from Spring Boot test utils.
* https://github.com/spring-projects/spring-boot/blob/1.4.x/spring-boot/src/test/java/org/springframework/boot/testutil/FilteredClassPathRunner.java
* @author Ryan Baxter
*/
public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
public FilteredClassPathRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
@Override
protected TestClass createTestClass(Class<?> testClass) {
try {
ClassLoader classLoader = createTestClassLoader(testClass);
return new FilteredTestClass(classLoader, testClass.getName());
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private URLClassLoader createTestClassLoader(Class<?> testClass) throws Exception {
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
return new FilteredClassLoader(filterUrls(extractUrls(classLoader), testClass),
classLoader.getParent(), classLoader);
}
private URL[] extractUrls(URLClassLoader classLoader) throws Exception {
List<URL> extractedUrls = new ArrayList<URL>();
for (URL url : classLoader.getURLs()) {
if (isSurefireBooterJar(url)) {
extractedUrls.addAll(extractUrlsFromManifestClassPath(url));
}
else {
extractedUrls.add(url);
}
}
return extractedUrls.toArray(new URL[extractedUrls.size()]);
}
private boolean isSurefireBooterJar(URL url) {
return url.getPath().contains("surefirebooter");
}
private List<URL> extractUrlsFromManifestClassPath(URL booterJar) throws Exception {
List<URL> urls = new ArrayList<URL>();
for (String entry : getClassPath(booterJar)) {
urls.add(new URL(entry));
}
return urls;
}
private String[] getClassPath(URL booterJar) throws Exception {
JarFile jarFile = new JarFile(new File(booterJar.toURI()));
try {
return StringUtils.delimitedListToStringArray(jarFile.getManifest()
.getMainAttributes().getValue(Attributes.Name.CLASS_PATH), " ");
}
finally {
jarFile.close();
}
}
private URL[] filterUrls(URL[] urls, Class<?> testClass) throws Exception {
ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass);
List<URL> filteredUrls = new ArrayList<URL>();
for (URL url : urls) {
if (!filter.isExcluded(url)) {
filteredUrls.add(url);
}
}
return filteredUrls.toArray(new URL[filteredUrls.size()]);
}
/**
* Filter for class path entries.
*/
private static final class ClassPathEntryFilter {
private final List<String> exclusions;
private final AntPathMatcher matcher = new AntPathMatcher();
private ClassPathEntryFilter(Class<?> testClass) throws Exception {
ClassPathExclusions exclusions = AnnotationUtils.findAnnotation(testClass,
ClassPathExclusions.class);
this.exclusions = exclusions == null ? Collections.<String>emptyList()
: Arrays.asList(exclusions.value());
}
private boolean isExcluded(URL url) throws Exception {
if (!"file".equals(url.getProtocol())) {
return false;
}
String name = new File(url.toURI()).getName();
for (String exclusion : this.exclusions) {
if (this.matcher.match(exclusion, name)) {
return true;
}
}
return false;
}
}
/**
* Filtered version of JUnit's {@link TestClass}.
*/
private static final class FilteredTestClass extends TestClass {
private final ClassLoader classLoader;
FilteredTestClass(ClassLoader classLoader, String testClassName)
throws ClassNotFoundException {
super(classLoader.loadClass(testClassName));
this.classLoader = classLoader;
}
@Override
public List<FrameworkMethod> getAnnotatedMethods(
Class<? extends Annotation> annotationClass) {
try {
return getAnnotatedMethods(annotationClass.getName());
}
catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@SuppressWarnings("unchecked")
private List<FrameworkMethod> getAnnotatedMethods(String annotationClassName)
throws ClassNotFoundException {
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) this.classLoader
.loadClass(annotationClassName);
List<FrameworkMethod> methods = super.getAnnotatedMethods(annotationClass);
return wrapFrameworkMethods(methods);
}
private List<FrameworkMethod> wrapFrameworkMethods(
List<FrameworkMethod> methods) {
List<FrameworkMethod> wrapped = new ArrayList<FrameworkMethod>(
methods.size());
for (FrameworkMethod frameworkMethod : methods) {
wrapped.add(new FilteredFrameworkMethod(this.classLoader,
frameworkMethod.getMethod()));
}
return wrapped;
}
}
/**
* Filtered version of JUnit's {@link FrameworkMethod}.
*/
private static final class FilteredFrameworkMethod extends FrameworkMethod {
private final ClassLoader classLoader;
private FilteredFrameworkMethod(ClassLoader classLoader, Method method) {
super(method);
this.classLoader = classLoader;
}
@Override
public Object invokeExplosively(Object target, Object... params)
throws Throwable {
ClassLoader originalClassLoader = Thread.currentThread()
.getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.classLoader);
try {
return super.invokeExplosively(target, params);
}
finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
}
}
private static final class FilteredClassLoader extends URLClassLoader {
private final ClassLoader junitLoader;
FilteredClassLoader(URL[] urls, ClassLoader parent, ClassLoader junitLoader) {
super(urls, parent);
this.junitLoader = junitLoader;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("org.junit")) {
return this.junitLoader.loadClass(name);
}
return super.loadClass(name);
}
}
}

View File

@@ -0,0 +1,157 @@
package org.springframework.cloud.client.loadbalancer;
import static org.hamcrest.MatcherAssert.assertThat;
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 java.io.IOException;
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 Ryan Baxter
*/
public abstract class AbstractLoadBalancerAutoConfigurationTests {
@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 abstract void assertLoadBalanced(RestTemplate restTemplate);
@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)
.properties("spring.aop.proxyTargetClass=true")
.sources(config, LoadBalancerAutoConfiguration.class).run();
}
@Configuration
protected static class OneRestTemplate {
@LoadBalanced
@Bean
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Bean
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();}
}
@Configuration
protected static class TwoRestTemplates {
@Primary
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Bean
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();}
@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,
this.random.nextInt(40000), false);
}
@Override
@SneakyThrows
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
return request.apply(choose(serviceId));
}
@Override
@SneakyThrows
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
return request.apply(choose(serviceId));
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return DefaultServiceInstance.getUri(instance);
}
}
}

View File

@@ -1,163 +1,29 @@
package org.springframework.cloud.client.loadbalancer;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
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.junit.runner.RunWith;
import org.springframework.cloud.ClassPathExclusions;
import org.springframework.cloud.FilteredClassPathRunner;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.retry.RetryPolicy;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.MatcherAssert.assertThat;
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 lombok.SneakyThrows;
/**
* @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);
}
@RunWith(FilteredClassPathRunner.class)
@ClassPathExclusions({"spring-retry-*.jar", "spring-boot-starter-aop-*.jar"})
public class LoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAutoConfigurationTests {
@Override
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)
.properties("spring.aop.proxyTargetClass=true")
.sources(config, LoadBalancerAutoConfiguration.class).run();
}
@Configuration
protected static class OneRestTemplate {
@LoadBalanced
@Bean
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Bean
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();}
}
@Configuration
protected static class TwoRestTemplates {
@Primary
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Bean
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();}
@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,
this.random.nextInt(40000), false);
}
@Override
@SneakyThrows
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
return request.apply(choose(serviceId));
}
@Override
@SneakyThrows
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
return request.apply(choose(serviceId));
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return DefaultServiceInstance.getUri(instance);
}
}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud.client.loadbalancer;
import java.util.List;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
/**
* @author Ryan Baxter
*/
public class RetryLoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAutoConfigurationTests {
@Override
protected void assertLoadBalanced(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
assertThat(interceptors, hasSize(1));
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
assertThat(interceptor, is(instanceOf(RetryLoadBalancerInterceptor.class)));
}
}

View File

@@ -1,5 +1,7 @@
package org.springframework.cloud.client.loadbalancer;
import java.io.IOException;
import java.net.URI;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -14,9 +16,6 @@ import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import java.io.IOException;
import java.net.URI;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any;
@@ -31,7 +30,7 @@ import static org.mockito.Mockito.when;
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
public class LoadBalancerInterceptorTest {
public class RetryLoadBalancerInterceptorTest {
private LoadBalancerClient client;
private RetryTemplate retryTemplate;
@@ -63,7 +62,7 @@ public class LoadBalancerInterceptorTest {
when(client.choose(eq("foo"))).thenReturn(serviceInstance);
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException());
lbProperties.setEnabled(false);
LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
interceptor.intercept(request, body, execution);
@@ -81,7 +80,7 @@ public class LoadBalancerInterceptorTest {
when(client.choose(eq("foo"))).thenReturn(serviceInstance);
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
interceptor.intercept(request, body, execution);
@@ -101,7 +100,7 @@ public class LoadBalancerInterceptorTest {
when(client.choose(eq("foo"))).thenReturn(serviceInstance);
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -122,7 +121,7 @@ public class LoadBalancerInterceptorTest {
when(client.choose(eq("foo"))).thenReturn(serviceInstance);
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -145,7 +144,7 @@ public class LoadBalancerInterceptorTest {
when(client.choose(eq("foo"))).thenReturn(serviceInstance);
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);