From b1b0ec86a6d6d0b370616bca1a3f73b141b1355c Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Tue, 5 Nov 2019 15:22:09 +0100 Subject: [PATCH] Fix gh 619 pass customisers in bean post processors (#627) * Switch to passing customisers in BeanPostProcessor instead of the SmartInitializingSingleton. * Do not instantiate ReactorLoadBalancerExchangeFilterFunction while registering LB BeanPostProcessor. * Do not instantiate ReactorLoadBalancerExchangeFilterFunction while registering LB BeanPostProcessor. * Extract BeanPostProcessor instantiation to a separate configuration. * Fix tests. * Switch to using generics for DeferringLoadBalancerExchangeFilterFunction and reusing BPP configuration for Ribbon. Add more test assertions. * Add javadocs. * Add test verifying LB filters added when WebClient built in constructor. --- ...ingLoadBalancerExchangeFilterFunction.java | 70 +++++++++++ ...cerBeanPostProcessorAutoConfiguration.java | 109 ++++++++++++++++++ ...ncerWebClientBuilderBeanPostProcessor.java | 58 ++++++++++ ...ReactiveLoadBalancerAutoConfiguration.java | 32 ----- ...orLoadBalancerClientAutoConfiguration.java | 37 +----- .../main/resources/META-INF/spring.factories | 1 + .../reactive/LoadBalancerTestUtils.java | 22 +++- ...iveLoadBalancerAutoConfigurationTests.java | 44 +++++-- ...dBalancerClientAutoConfigurationTests.java | 38 ++++-- .../config/LoadBalancerAutoConfiguration.java | 2 + 10 files changed, 324 insertions(+), 89 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/DeferringLoadBalancerExchangeFilterFunction.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientBuilderBeanPostProcessor.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/DeferringLoadBalancerExchangeFilterFunction.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/DeferringLoadBalancerExchangeFilterFunction.java new file mode 100644 index 00000000..ce7b41ed --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/DeferringLoadBalancerExchangeFilterFunction.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.reactive; + +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; + +/** + * An {@link ExchangeFilterFunction} implementation that uses {@link ObjectProvider} to + * resolve appropriate load-balancing {@link ExchangeFilterFunction} delegate when the + * {@link ExchangeFilterFunction#filter(ClientRequest, ExchangeFunction)} method is first + * called. + * + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +public class DeferringLoadBalancerExchangeFilterFunction + implements ExchangeFilterFunction { + + private final ObjectProvider exchangeFilterFunctionProvider; + + private T delegate; + + public DeferringLoadBalancerExchangeFilterFunction( + ObjectProvider exchangeFilterFunctionProvider) { + this.exchangeFilterFunctionProvider = exchangeFilterFunctionProvider; + } + + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) { + tryResolveDelegate(); + return delegate.filter(request, next); + } + + // Visible for tests + void tryResolveDelegate() { + if (delegate == null) { + delegate = exchangeFilterFunctionProvider.getIfAvailable(); + if (delegate == null) { + throw new IllegalStateException( + "ReactorLoadBalancerExchangeFilterFunction not available."); + } + } + } + + // Visible for tests + T getDelegate() { + return delegate; + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java new file mode 100644 index 00000000..308e42d8 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.reactive; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +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.ConditionalOnMissingBean; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * An auto-configuration that provides a {@link BeanPostProcessor} that allows the use of + * a {@link LoadBalanced} {@link WebClient.Builder} with + * {@link ReactorLoadBalancerExchangeFilterFunction} and {@link ReactiveLoadBalancer} used + * under the hood. NOTE: This has been extracted to a separate configuration in order to + * not impact instantiation and post-processing of other Reactor-LoadBalancer-related + * beans. + * + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +@Configuration +@ConditionalOnClass(WebClient.class) +@Conditional(LoadBalancerBeanPostProcessorAutoConfiguration.OnAnyLoadBalancerImplementationPresentCondition.class) +public class LoadBalancerBeanPostProcessorAutoConfiguration { + + @Bean + public LoadBalancerWebClientBuilderBeanPostProcessor loadBalancerWebClientBuilderBeanPostProcessor( + DeferringLoadBalancerExchangeFilterFunction deferringExchangeFilterFunction, + ApplicationContext context) { + return new LoadBalancerWebClientBuilderBeanPostProcessor( + deferringExchangeFilterFunction, context); + } + + @Configuration + @Conditional(ReactorLoadBalancerClientAutoConfiguration.OnNoRibbonDefaultCondition.class) + @ConditionalOnBean(ReactiveLoadBalancer.Factory.class) + protected static class ReactorDeferringLoadBalancerFilterConfig { + + @Bean + @Primary + DeferringLoadBalancerExchangeFilterFunction reactorDeferringLoadBalancerExchangeFilterFunction( + ObjectProvider exchangeFilterFunctionProvider) { + return new DeferringLoadBalancerExchangeFilterFunction<>( + exchangeFilterFunctionProvider); + } + + } + + @Configuration + @ConditionalOnBean(LoadBalancerClient.class) + @AutoConfigureAfter(ReactorDeferringLoadBalancerFilterConfig.class) + @Deprecated + protected static class ReactiveLoadBalancerConfig { + + @Bean + @ConditionalOnMissingBean + DeferringLoadBalancerExchangeFilterFunction deferringLoadBalancerExchangeFilterFunction( + ObjectProvider exchangeFilterFunctionProvider) { + return new DeferringLoadBalancerExchangeFilterFunction<>( + exchangeFilterFunctionProvider); + } + + } + + static final class OnAnyLoadBalancerImplementationPresentCondition + extends AnyNestedCondition { + + private OnAnyLoadBalancerImplementationPresentCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(ReactiveLoadBalancer.Factory.class) + static class ReactiveLoadBalancerFactoryPresent { + + } + + @ConditionalOnBean(LoadBalancerClient.class) + static class LoadBalancerClientPresent { + + } + + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientBuilderBeanPostProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientBuilderBeanPostProcessor.java new file mode 100644 index 00000000..60884ea1 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientBuilderBeanPostProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.reactive; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.ApplicationContext; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * A {@link BeanPostProcessor} that applies + * {@link DeferringLoadBalancerExchangeFilterFunction} filter to all + * {@link WebClient.Builder} instances annotated with {@link LoadBalanced}. + * + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +public class LoadBalancerWebClientBuilderBeanPostProcessor implements BeanPostProcessor { + + private final DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction; + + private final ApplicationContext context; + + public LoadBalancerWebClientBuilderBeanPostProcessor( + DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction, + ApplicationContext context) { + this.exchangeFilterFunction = exchangeFilterFunction; + this.context = context; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof WebClient.Builder) { + if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) { + return bean; + } + ((WebClient.Builder) bean).filter(exchangeFilterFunction); + } + return bean; + } + +} 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 e2f0ed2f..5be33bd5 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 @@ -16,16 +16,10 @@ package org.springframework.cloud.client.loadbalancer.reactive; -import java.util.Collections; -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; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,32 +38,6 @@ import org.springframework.web.reactive.function.client.WebClient; @Deprecated public class ReactiveLoadBalancerAutoConfiguration { - @LoadBalanced - @Autowired(required = false) - private List webClientBuilders = Collections.emptyList(); - - public 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( - LoadBalancerExchangeFilterFunction filterFunction) { - return builder -> builder.filter(filterFunction); - } - @Bean public LoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction( LoadBalancerClient client) { 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 ded6bc62..f7108fbe 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 @@ -16,16 +16,11 @@ 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; @@ -63,45 +58,15 @@ public class ReactorLoadBalancerClientAutoConfiguration { @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; - } - } - private static final class OnNoRibbonDefaultCondition extends AnyNestedCondition { + static final class OnNoRibbonDefaultCondition extends AnyNestedCondition { private OnNoRibbonDefaultCondition() { super(ConfigurationPhase.REGISTER_BEAN); diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories index bd9662cb..19ea024b 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscove org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\ +org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\ org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\ diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerTestUtils.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerTestUtils.java index c1ab9d8e..8a3b4548 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerTestUtils.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerTestUtils.java @@ -18,6 +18,8 @@ package org.springframework.cloud.client.loadbalancer.reactive; import java.util.List; +import org.apache.commons.lang3.ArrayUtils; + import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -39,9 +41,10 @@ final class LoadBalancerTestUtils { throw new IllegalStateException("Can't instantiate a utility class"); } - static ConfigurableApplicationContext init(Class config, Class clientClass) { + static ConfigurableApplicationContext init(Class... configClasses) { return new SpringApplicationBuilder().web(WebApplicationType.NONE) - .sources(config, WebClientAutoConfiguration.class, clientClass).run(); + .sources(ArrayUtils.add(configClasses, WebClientAutoConfiguration.class)) + .run(); } @SuppressWarnings("unchecked") @@ -54,8 +57,19 @@ final class LoadBalancerTestUtils { Class exchangeFilterFunctionClass) { List filters = getFilters(webClientBuilder); then(filters).hasSize(1); - ExchangeFilterFunction interceptor = filters.get(0); - then(interceptor).isInstanceOf(exchangeFilterFunctionClass); + then(filters.get(0)) + .isInstanceOf(DeferringLoadBalancerExchangeFilterFunction.class); + DeferringLoadBalancerExchangeFilterFunction interceptor = (DeferringLoadBalancerExchangeFilterFunction) filters + .get(0); + interceptor.tryResolveDelegate(); + then(interceptor.getDelegate()).isInstanceOf(exchangeFilterFunctionClass); + } + + static void assertLoadBalanced(WebClient webClient, + Class exchangeFilterFunctionClass) { + assertLoadBalanced( + (WebClient.Builder) ReflectionTestUtils.getField(webClient, "builder"), + exchangeFilterFunctionClass); } } 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 49f03ef5..663ad145 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 @@ -23,6 +23,7 @@ import java.util.Random; import org.junit.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; @@ -36,7 +37,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.web.reactive.function.client.WebClient; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.assertLoadBalanced; import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.getFilters; /** @@ -56,7 +59,15 @@ public class ReactiveLoadBalancerAutoConfigurationTests { WebClient.Builder webClientBuilder = webClientBuilders.values().iterator().next(); then(webClientBuilder).isNotNull(); - assertLoadBalanced(webClientBuilder); + assertLoadBalanced(webClientBuilder, LoadBalancerExchangeFilterFunction.class); + + final Map testServiceMap = context + .getBeansOfType(OneWebClientBuilder.TestService.class); + then(testServiceMap).isNotNull().hasSize(1); + OneWebClientBuilder.TestService testService = testServiceMap.values().stream() + .findFirst().get(); + assertLoadBalanced(testService.webClient, + LoadBalancerExchangeFilterFunction.class); } @Test @@ -70,7 +81,7 @@ public class ReactiveLoadBalancerAutoConfigurationTests { TwoWebClientBuilders.Two two = context.getBean(TwoWebClientBuilders.Two.class); then(two.loadBalanced).isNotNull(); - assertLoadBalanced(two.loadBalanced); + assertLoadBalanced(two.loadBalanced, LoadBalancerExchangeFilterFunction.class); then(two.nonLoadBalanced).isNotNull(); then(getFilters(two.nonLoadBalanced)).isNullOrEmpty(); @@ -99,20 +110,20 @@ public class ReactiveLoadBalancerAutoConfigurationTests { then(webClientBuilders).hasSize(1); + assertThatThrownBy( + () -> context.getBean(LoadBalancerExchangeFilterFunction.class)) + .isInstanceOf(NoSuchBeanDefinitionException.class); + WebClient.Builder builder = context.getBean(WebClient.Builder.class); then(builder).isNotNull(); - then(getFilters(builder)).isNullOrEmpty(); + assertLoadBalanced(builder, ReactorLoadBalancerExchangeFilterFunction.class); } private ConfigurableApplicationContext init(Class config) { return LoadBalancerTestUtils.init(config, - ReactiveLoadBalancerAutoConfiguration.class); - } - - private void assertLoadBalanced(WebClient.Builder builder) { - LoadBalancerTestUtils.assertLoadBalanced(builder, - LoadBalancerExchangeFilterFunction.class); + ReactiveLoadBalancerAutoConfiguration.class, + LoadBalancerBeanPostProcessorAutoConfiguration.class); } @Configuration @@ -156,6 +167,21 @@ public class ReactiveLoadBalancerAutoConfigurationTests { return WebClient.builder(); } + @Bean + TestService testService() { + return new TestService(loadBalancedWebClientBuilder()); + } + + private final class TestService { + + public final WebClient webClient; + + private TestService(WebClient.Builder builder) { + this.webClient = builder.build(); + } + + } + } @Configuration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java index 876a183f..e5a97e27 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java @@ -31,6 +31,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.assertLoadBalanced; import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.getFilters; /** @@ -50,7 +51,16 @@ public class ReactorLoadBalancerClientAutoConfigurationTests { WebClient.Builder webClientBuilder = webClientBuilders.values().iterator().next(); then(webClientBuilder).isNotNull(); - assertLoadBalanced(webClientBuilder); + assertLoadBalanced(webClientBuilder, + ReactorLoadBalancerExchangeFilterFunction.class); + + final Map testServiceMap = context + .getBeansOfType(OneWebClientBuilder.TestService.class); + then(testServiceMap).isNotNull().hasSize(1); + OneWebClientBuilder.TestService testService = testServiceMap.values().stream() + .findFirst().get(); + assertLoadBalanced(testService.webClient, + ReactorLoadBalancerExchangeFilterFunction.class); } @Test @@ -64,7 +74,8 @@ public class ReactorLoadBalancerClientAutoConfigurationTests { TwoWebClientBuilders.Two two = context.getBean(TwoWebClientBuilders.Two.class); then(two.loadBalanced).isNotNull(); - assertLoadBalanced(two.loadBalanced); + assertLoadBalanced(two.loadBalanced, + ReactorLoadBalancerExchangeFilterFunction.class); then(two.nonLoadBalanced).isNotNull(); then(getFilters(two.nonLoadBalanced)).isNullOrEmpty(); @@ -86,12 +97,8 @@ public class ReactorLoadBalancerClientAutoConfigurationTests { private ConfigurableApplicationContext init(Class config) { return LoadBalancerTestUtils.init(config, - ReactorLoadBalancerClientAutoConfiguration.class); - } - - private void assertLoadBalanced(WebClient.Builder webClientBuilder) { - LoadBalancerTestUtils.assertLoadBalanced(webClientBuilder, - ReactorLoadBalancerExchangeFilterFunction.class); + ReactorLoadBalancerClientAutoConfiguration.class, + LoadBalancerBeanPostProcessorAutoConfiguration.class); } @Configuration @@ -119,6 +126,21 @@ public class ReactorLoadBalancerClientAutoConfigurationTests { return WebClient.builder(); } + @Bean + TestService testService() { + return new TestService(loadBalancedWebClientBuilder()); + } + + private final class TestService { + + public final WebClient webClient; + + private TestService(WebClient.Builder builder) { + this.webClient = builder.build(); + } + + } + } @Configuration diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java index bd08cdd0..6ac05a8e 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; @@ -36,6 +37,7 @@ import org.springframework.context.annotation.Configuration; @Configuration @LoadBalancerClients @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class, + LoadBalancerBeanPostProcessorAutoConfiguration.class, ReactiveLoadBalancerAutoConfiguration.class }) public class LoadBalancerAutoConfiguration {