From 87fa3c78977000935263af6693dd91eee72629f9 Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Mon, 23 Sep 2019 13:51:32 +0200 Subject: [PATCH] Use Ribbon by default. (#609) * Use Ribbon by default. * Fix after review. * Reformat. --- .../main/asciidoc/spring-cloud-commons.adoc | 15 ++- ...ReactiveLoadBalancerAutoConfiguration.java | 4 +- ...orLoadBalancerClientAutoConfiguration.java | 113 ++++++++++++++---- ...iveLoadBalancerAutoConfigurationTests.java | 8 +- ...ngLoadBalancerClientAutoConfiguration.java | 14 ++- ...dBalancerClientAutoConfigurationTests.java | 8 +- 6 files changed, 122 insertions(+), 40 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index f737493e..1f337843 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -407,16 +407,19 @@ public class MyClass { ---- The URI needs to use a virtual host name (that is, a service name, not a host name). -The Ribbon client is used to create a full physical address. +The Ribbon client or Spring Cloud LoadBalancer is used to create a full physical address. IMPORTANT: If you want to use a `@LoadBalanced WebClient.Builder`, you need to have a loadbalancer implementation in the classpath. It is recommended that you add the `org.springframework.cloud:spring-cloud-starter-loadbalancer` dependency to your project. Then, `ReactiveLoadBalancer` will be used underneath. -Alternatively, this functionality will also work with spring-cloud-starter-netflix-ribbon, but the request +Alternatively, this functionality will also work with `spring-cloud-starter-netflix-ribbon`, but the request will be handled by a non-reactive `LoadBalancerClient` under the hood. Additionally, spring-cloud-starter-netflix-ribbon is already in maintenance mode, so we do not recommend adding it to new projects. +If you have both `spring-cloud-starter-loadbalancer` and `spring-cloud-starter-netflix-ribbon` +in your classpath, Ribbon will be used by default. In order to switch to Spring Cloud LoadBalancer, +set the value of `spring.cloud.loadbalancer.ribbon.enabled` to false. IMPORTANT: In order to make use of the more efficient cached version of `ServiceInstanceListSupplier`, `spring-cloud-starter-loadbalancer` will *enable caching* by default. @@ -565,8 +568,14 @@ public class MyClass { ---- The URI needs to use a virtual host name (that is, a service name, not a host name). -The `ReactorLoadBalancerClient` is used to create a full physical address. +The `ReactorLoadBalancer` is used to create a full physical address. + +NOTE: If you have `spring-cloud-netflix-ribbon` in your classpath, <> +will be used by default to maintain backward compatibility. In order to be able to use +`ReactorLoadBalancerExchangeFilterFunction`, set the value of `spring.cloud.loadbalancer.ribbon.enabled` property +to `false`. +[[load-balancer-exchange-filter-function]] ==== Spring WebFlux WebClient with non-reactive Load Balancer Client If you you don't have `org.springframework.cloud:spring-cloud-starter-loadbalancer` in your project, diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfiguration.java index 1a3a3c78..e2f0ed2f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -38,7 +39,8 @@ import org.springframework.web.reactive.function.client.WebClient; @Configuration @ConditionalOnClass(WebClient.class) @ConditionalOnBean(LoadBalancerClient.class) -@ConditionalOnMissingBean(ReactiveLoadBalancer.Factory.class) +@AutoConfigureAfter(ReactorLoadBalancerClientAutoConfiguration.class) +@ConditionalOnMissingBean(ReactorLoadBalancerExchangeFilterFunction.class) @Deprecated public class ReactiveLoadBalancerAutoConfiguration { diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java index 5ef9ba3f..ebb752cb 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java @@ -19,12 +19,21 @@ package org.springframework.cloud.client.loadbalancer.reactive; import java.util.Collections; import java.util.List; +import javax.annotation.PostConstruct; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; @@ -41,40 +50,92 @@ import org.springframework.web.reactive.function.client.WebClient; @ConditionalOnBean(ReactiveLoadBalancer.Factory.class) public class ReactorLoadBalancerClientAutoConfiguration { - private List webClientBuilders = Collections.emptyList(); - - List getBuilders() { - return this.webClientBuilders; + @Bean + @ConditionalOnClass( + name = "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient") + public ReactorLoadBalancerClientRibbonWarnLogger reactorLoadBalancerClientRibbonWarnLogger() { + return new ReactorLoadBalancerClientRibbonWarnLogger(); } - @Bean - public SmartInitializingSingleton loadBalancedWebClientInitializer( - final List customizers) { - return () -> { - for (WebClient.Builder webClientBuilder : getBuilders()) { - for (WebClientCustomizer customizer : customizers) { - customizer.customize(webClientBuilder); + @Configuration + @Conditional(OnNoRibbonDefaultCondition.class) + protected static class ReactorLoadBalancerExchangeFilterFunctionConfig { + + private List webClientBuilders = Collections.emptyList(); + + List getBuilders() { + return this.webClientBuilders; + } + + @Bean + public SmartInitializingSingleton loadBalancedWebClientInitializer( + final List customizers) { + return () -> { + for (WebClient.Builder webClientBuilder : getBuilders()) { + for (WebClientCustomizer customizer : customizers) { + customizer.customize(webClientBuilder); + } } - } - }; - } + }; + } + + @Bean + public WebClientCustomizer loadBalancerClientWebClientCustomizer( + ReactorLoadBalancerExchangeFilterFunction filterFunction) { + return builder -> builder.filter(filterFunction); + } + + @Bean + public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction( + ReactiveLoadBalancer.Factory loadBalancerFactory) { + return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory); + } + + @LoadBalanced + @Autowired(required = false) + void setWebClientBuilders(List webClientBuilders) { + this.webClientBuilders = webClientBuilders; + } - @Bean - public WebClientCustomizer loadBalancerClientWebClientCustomizer( - ReactorLoadBalancerExchangeFilterFunction filterFunction) { - return builder -> builder.filter(filterFunction); } - @Bean - public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction( - ReactiveLoadBalancer.Factory loadBalancerFactory) { - return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory); + private static final class OnNoRibbonDefaultCondition extends AnyNestedCondition { + + private OnNoRibbonDefaultCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled", + havingValue = "false") + static class RibbonNotEnabled { + + } + + @ConditionalOnMissingClass("org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient") + static class RibbonLoadBalancerNotPresent { + + } + } - @LoadBalanced - @Autowired(required = false) - void setWebClientBuilders(List webClientBuilders) { - this.webClientBuilders = webClientBuilders; + private static class ReactorLoadBalancerClientRibbonWarnLogger { + + private static final Log LOG = LogFactory + .getLog(ReactorLoadBalancerClientRibbonWarnLogger.class); + + @PostConstruct + void logWarning() { + if (LOG.isWarnEnabled()) { + LOG.warn("You have RibbonLoadBalancerClient on your classpath. " + + "LoadBalancerExchangeFilterFunction that uses it under the " + + "hood will be used by default. Spring Cloud Ribbon is now in maintenance mode, " + + "so we suggest switching to " + + ReactorLoadBalancerExchangeFilterFunction.class.getSimpleName() + + " instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or " + + "remove spring-cloud-starter-netflix-ribbon from your project."); + } + } + } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfigurationTests.java index 44e51432..49f03ef5 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancerAutoConfigurationTests.java @@ -91,7 +91,7 @@ public class ReactiveLoadBalancerAutoConfigurationTests { } @Test - public void autoConfigurationNotLoadedWhenReactorLoadBalancerClientPresent() { + public void autoConfigurationNotLoadedWhenReactorLoadBalancerExchangeFilterFunctionPresent() { ConfigurableApplicationContext context = init( ReactorLoadBalancerClientPresent.class); final Map webClientBuilders = context @@ -139,6 +139,12 @@ public class ReactiveLoadBalancerAutoConfigurationTests { return serviceId -> new TestReactiveLoadBalancer(); } + @Bean + ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction() { + return new ReactorLoadBalancerExchangeFilterFunction( + reactiveLoadBalancerFactory()); + } + } @Configuration diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java index 8af08a6c..e677eabf 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java @@ -55,8 +55,8 @@ public class BlockingLoadBalancerClientAutoConfiguration { @Bean @ConditionalOnClass( name = "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient") - public RibbonWarnLogger ribbonWarnLogger() { - return new RibbonWarnLogger(); + public BlockingLoadBalancerClientRibbonWarnLogger blockingLoadBalancerClientRibbonWarnLogger() { + return new BlockingLoadBalancerClientRibbonWarnLogger(); } @Configuration @@ -93,17 +93,19 @@ public class BlockingLoadBalancerClientAutoConfiguration { } - static class RibbonWarnLogger { + static class BlockingLoadBalancerClientRibbonWarnLogger { - private static final Log LOG = LogFactory.getLog(RibbonWarnLogger.class); + private static final Log LOG = LogFactory + .getLog(BlockingLoadBalancerClientRibbonWarnLogger.class); @PostConstruct void logWarning() { if (LOG.isWarnEnabled()) { LOG.warn( - "You already have RibbonLoadBalancerClient on your classpath. It will be used by default. To use " + "You already have RibbonLoadBalancerClient on your classpath. It will be used by default. " + + "As Spring Cloud Ribbon is in maintenance mode. We recommend switching to " + BlockingLoadBalancerClient.class.getSimpleName() - + " set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or " + + " instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or " + "remove spring-cloud-starter-netflix-ribbon from your project."); } } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java index cc945371..20936266 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java @@ -22,7 +22,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; -import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration.RibbonWarnLogger; +import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration.BlockingLoadBalancerClientRibbonWarnLogger; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,8 @@ public class BlockingLoadBalancerClientAutoConfigurationTests { public void beansCreatedNormally() { applicationContextRunner.run(ctxt -> { assertThat(ctxt).hasSingleBean(BlockingLoadBalancerClient.class); - assertThat(ctxt).doesNotHaveBean(RibbonWarnLogger.class); + assertThat(ctxt) + .doesNotHaveBean(BlockingLoadBalancerClientRibbonWarnLogger.class); }); } @@ -54,7 +55,8 @@ public class BlockingLoadBalancerClientAutoConfigurationTests { .withClassLoader(new FilteredClassLoader(RestTemplate.class)) .run(context -> { assertThat(context).doesNotHaveBean(BlockingLoadBalancerClient.class); - assertThat(context).doesNotHaveBean(RibbonWarnLogger.class); + assertThat(context).doesNotHaveBean( + BlockingLoadBalancerClientRibbonWarnLogger.class); }); }