From 39045ee20e9638ff217828483fa1886373b44444 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 20 Oct 2016 14:36:39 -0400 Subject: [PATCH] Making spring-retry optional by conditional loading config --- .../LoadBalancerAutoConfiguration.java | 88 ++++--- .../loadbalancer/LoadBalancerInterceptor.java | 80 +------ .../RetryLoadBalancerInterceptor.java | 77 ++++++ .../loadbalancer/ServiceRequestWrapper.java | 29 +++ .../cloud/ClassPathExclusions.java | 25 ++ .../cloud/FilteredClassPathRunner.java | 220 ++++++++++++++++++ ...actLoadBalancerAutoConfigurationTests.java | 157 +++++++++++++ .../LoadBalancerAutoConfigurationTests.java | 150 +----------- ...tryLoadBalancerAutoConfigurationTests.java | 25 ++ ... => RetryLoadBalancerInterceptorTest.java} | 17 +- 10 files changed, 620 insertions(+), 248 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ServiceRequestWrapper.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathExclusions.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerAutoConfigurationTests.java rename spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/{LoadBalancerInterceptorTest.java => RetryLoadBalancerInterceptorTest.java} (91%) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java index f2d73c06..a0d51d93 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java @@ -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 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 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 list = new ArrayList<>( + restTemplate.getInterceptors()); + list.add(loadBalancerInterceptor); + restTemplate.setInterceptors(list); + } + }; + } } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptor.java index b7846afb..0d989ae0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptor.java @@ -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() { + String serviceName = originalUri.getHost(); + return this.loadBalancer.execute(serviceName, + new LoadBalancerRequest() { @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() { - - @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; - } - - } - } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java new file mode 100644 index 00000000..a5cae45f --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java @@ -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() { + @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() { + + @Override + public ClientHttpResponse apply( + final ServiceInstance instance) + throws Exception { + HttpRequest serviceRequest = new ServiceRequestWrapper( + request, instance, loadBalancer); + return execution.execute(serviceRequest, body); + } + + }); + } + }); + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ServiceRequestWrapper.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ServiceRequestWrapper.java new file mode 100644 index 00000000..37be07f5 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ServiceRequestWrapper.java @@ -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; + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathExclusions.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathExclusions.java new file mode 100644 index 00000000..88f151e3 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/ClassPathExclusions.java @@ -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(); +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java new file mode 100644 index 00000000..2f432a54 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/FilteredClassPathRunner.java @@ -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 extractedUrls = new ArrayList(); + 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 extractUrlsFromManifestClassPath(URL booterJar) throws Exception { + List urls = new ArrayList(); + 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 filteredUrls = new ArrayList(); + 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 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.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 getAnnotatedMethods( + Class annotationClass) { + try { + return getAnnotatedMethods(annotationClass.getName()); + } + catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + @SuppressWarnings("unchecked") + private List getAnnotatedMethods(String annotationClassName) + throws ClassNotFoundException { + Class annotationClass = (Class) this.classLoader + .loadClass(annotationClassName); + List methods = super.getAnnotatedMethods(annotationClass); + return wrapFrameworkMethods(methods); + } + + private List wrapFrameworkMethods( + List methods) { + List wrapped = new ArrayList( + 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); + } + + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java new file mode 100644 index 00000000..52f5a290 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java @@ -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 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 restTemplates = context + .getBeansOfType(RestTemplate.class); + + assertThat(restTemplates, is(notNullValue())); + Collection 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 execute(String serviceId, LoadBalancerRequest request) { + return request.apply(choose(serviceId)); + } + + @Override + @SneakyThrows + public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException { + return request.apply(choose(serviceId)); + } + + @Override + public URI reconstructURI(ServiceInstance instance, URI original) { + return DefaultServiceInstance.getUri(instance); + } + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfigurationTests.java index b1ecdf07..73b8cdba 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfigurationTests.java @@ -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 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 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 restTemplates = context - .getBeansOfType(RestTemplate.class); - - assertThat(restTemplates, is(notNullValue())); - Collection 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 execute(String serviceId, LoadBalancerRequest request) { - return request.apply(choose(serviceId)); - } - - @Override - @SneakyThrows - public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException { - return request.apply(choose(serviceId)); - } - - @Override - public URI reconstructURI(ServiceInstance instance, URI original) { - return DefaultServiceInstance.getUri(instance); - } - } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerAutoConfigurationTests.java new file mode 100644 index 00000000..9d51bbb2 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerAutoConfigurationTests.java @@ -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 interceptors = restTemplate.getInterceptors(); + assertThat(interceptors, hasSize(1)); + ClientHttpRequestInterceptor interceptor = interceptors.get(0); + assertThat(interceptor, is(instanceOf(RetryLoadBalancerInterceptor.class))); + } +} + diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptorTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java similarity index 91% rename from spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptorTest.java rename to spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java index 3ad2a90f..64a8aff6 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerInterceptorTest.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java @@ -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);