From c8fe5d38ef6f09d2f5f896f5335a97efe7426502 Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Tue, 16 Jan 2024 14:18:38 +0100 Subject: [PATCH] Use SmartInitializingSingleton instead of BeanPostProcessor to set up @LoadBalanced WebClient.Builder. (#1319) --- .../LoadBalancerAutoConfiguration.java | 38 ++++++++++++------ ...cerRestClientBuilderBeanPostProcessor.java | 4 +- .../RestClientBuilderCustomizer.java | 33 ++++++++++++++++ ...actLoadBalancerAutoConfigurationTests.java | 39 ++++++++++++++++++- .../LoadBalancerEnvironmentPropertyUtils.java | 2 +- src/checkstyle/checkstyle-suppressions.xml | 2 + 6 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RestClientBuilderCustomizer.java 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 615228124..7c2064fc3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -32,12 +33,12 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; -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.http.client.ClientHttpRequestInterceptor; import org.springframework.retry.support.RetryTemplate; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** @@ -49,7 +50,7 @@ * @author Gang Li * @author Olga Maciaszek-Sharma */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @Conditional(BlockingRestClassesPresentCondition.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerClientsProperties.class) @@ -59,6 +60,10 @@ public class LoadBalancerAutoConfiguration { @Autowired(required = false) private List restTemplates = Collections.emptyList(); + @LoadBalanced + @Autowired(required = false) + private List restClientBuilders = Collections.emptyList(); + @Autowired(required = false) private List transformers = Collections.emptyList(); @@ -74,6 +79,19 @@ public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( }); } + @Bean + public SmartInitializingSingleton loadBalancedRestClientBuilderInitializer( + final ObjectProvider> restClientBuilderCustomizers) { + return () -> restClientBuilderCustomizers.ifAvailable(customizers -> { + for (RestClient.Builder restClientBuilder : LoadBalancerAutoConfiguration.this.restClientBuilders) { + for (RestClientBuilderCustomizer customizer : customizers) { + customizer.customize(restClientBuilder); + } + + } + }); + } + @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { @@ -101,11 +119,10 @@ public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerIntercept } @Bean - @ConditionalOnBean(LoadBalancerInterceptor.class) @ConditionalOnMissingBean - LoadBalancerRestClientBuilderBeanPostProcessor lbRestClientPostProcessor( - final LoadBalancerInterceptor loadBalancerInterceptor, ApplicationContext context) { - return new LoadBalancerRestClientBuilderBeanPostProcessor(loadBalancerInterceptor, context); + public RestClientBuilderCustomizer restClientBuilderCustomizer( + final LoadBalancerInterceptor loadBalancerInterceptor) { + return restClientBuilder -> restClientBuilder.requestInterceptor(loadBalancerInterceptor); } } @@ -174,11 +191,10 @@ public RestTemplateCustomizer restTemplateCustomizer( } @Bean - @ConditionalOnBean(RetryLoadBalancerInterceptor.class) @ConditionalOnMissingBean - LoadBalancerRestClientBuilderBeanPostProcessor lbRestClientPostProcessor( - final RetryLoadBalancerInterceptor loadBalancerInterceptor, ApplicationContext context) { - return new LoadBalancerRestClientBuilderBeanPostProcessor(loadBalancerInterceptor, context); + public RestClientBuilderCustomizer restClientBuilderCustomizer( + final RetryLoadBalancerInterceptor loadBalancerInterceptor) { + return restClientBuilder -> restClientBuilder.requestInterceptor(loadBalancerInterceptor); } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientBuilderBeanPostProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientBuilderBeanPostProcessor.java index 6bd63cd72..5d5bf4c97 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientBuilderBeanPostProcessor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientBuilderBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -28,7 +28,9 @@ * * @author Olga Maciaszek-Sharma * @since 4.1.0 + * @deprecated to be removed in the next release. */ +@Deprecated public class LoadBalancerRestClientBuilderBeanPostProcessor implements BeanPostProcessor { private final ClientHttpRequestInterceptor loadBalancerInterceptor; diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RestClientBuilderCustomizer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RestClientBuilderCustomizer.java new file mode 100644 index 000000000..0273d1882 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RestClientBuilderCustomizer.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2024 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; + +import org.springframework.web.client.RestClient; + +/** + * A customizer interface for {@link RestClient.Builder}. Used to set + * {@link LoadBalancerInterceptor} on the builder at the end of the singleton + * pre-instantiation phase. + * + * @author Olga Maciaszek-Sharma + * @since 4.1.1 + */ +public interface RestClientBuilderCustomizer { + + void customize(RestClient.Builder restClientBuilder); + +} 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 index c81c40b4d..c92ba95db 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2024 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. @@ -117,6 +117,43 @@ void multipleRestClientBuilders() { }); } + @Test + void restTemplatesAndRestClientsFromUsersAutoConfiguration() { + applicationContextRunner + .withConfiguration(AutoConfigurations.of(TwoRestTemplatesAndTwoRestClientBuilders.class)) + .run(context -> { + final Map restClientBuilders = context + .getBeansOfType(RestClient.Builder.class); + final Map restTemplates = context.getBeansOfType(RestTemplate.class); + + assertThat(restClientBuilders).isNotNull(); + assertThat(restClientBuilders.values()).hasSize(2); + + TwoRestTemplatesAndTwoRestClientBuilders.Two two = context + .getBean(TwoRestTemplatesAndTwoRestClientBuilders.Two.class); + + assertThat(two.loadBalancedRestClientBuilder).isNotNull(); + assertLoadBalanced(two.loadBalancedRestClientBuilder); + + assertThat(two.nonLoadBalancedRestClientBuilder).isNotNull(); + two.nonLoadBalancedRestClientBuilder + .requestInterceptors(interceptors -> assertThat(interceptors).isEmpty()); + + assertThat(restTemplates).isNotNull(); + Collection templates = restTemplates.values(); + assertThat(templates).hasSize(2); + + TwoRestTemplatesAndTwoRestClientBuilders.Two twoRestTemplate = context + .getBean(TwoRestTemplatesAndTwoRestClientBuilders.Two.class); + + assertThat(twoRestTemplate.loadBalanced).isNotNull(); + assertLoadBalanced(twoRestTemplate.loadBalanced); + + assertThat(twoRestTemplate.nonLoadBalanced).isNotNull(); + assertThat(twoRestTemplate.nonLoadBalanced.getInterceptors()).isEmpty(); + }); + } + protected abstract void assertLoadBalanced(RestClient.Builder restClientBuilder); protected abstract void assertLoadBalanced(RestTemplate restTemplate); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerEnvironmentPropertyUtils.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerEnvironmentPropertyUtils.java index 3856dc674..60da875a0 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerEnvironmentPropertyUtils.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerEnvironmentPropertyUtils.java @@ -24,7 +24,7 @@ public final class LoadBalancerEnvironmentPropertyUtils { private LoadBalancerEnvironmentPropertyUtils() { - throw new IllegalStateException("Should not instantiate a utility class"); + throw new UnsupportedOperationException("Cannot instantiate a utility class"); } public static boolean trueForClientOrDefault(Environment environment, String propertySuffix) { diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 9d2f3dc55..24afedc95 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -18,5 +18,7 @@ + +