From 84cae9d30a9f8972c134bb601b3ae4cc557e1a37 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 21 Oct 2021 23:15:34 -0400 Subject: [PATCH 1/8] Adds config per load balancer client. Client specific configuration goes under `spring.cloud.loadbalancer.clients..*`. Defaults are set using `spring.cloud.loadbalancer.*`. Fixes gh-914 --- .../loadbalancer/BindingHandlerAdvise.java | 93 ++++++++++ .../LoadBalancerAutoConfiguration.java | 9 +- .../LoadBalancerClientsProperties.java | 168 ++++++++++++++++++ .../RetryLoadBalancerInterceptor.java | 25 ++- .../reactive/LoadBalancerRetryPolicy.java | 5 + .../reactive/ReactiveLoadBalancer.java | 5 + ...orLoadBalancerClientAutoConfiguration.java | 37 +++- ...torLoadBalancerExchangeFilterFunction.java | 15 +- ...FilterFunctionLoadBalancerRetryPolicy.java | 17 ++ ...bleLoadBalancerExchangeFilterFunction.java | 46 +++-- ...actLoadBalancerAutoConfigurationTests.java | 15 +- .../RetryLoadBalancerInterceptorTests.java | 50 +++--- ...dBalancerClientRequestTransformerTest.java | 5 +- ...dBalancerClientAutoConfigurationTests.java | 30 +++- ...adBalancerExchangeFilterFunctionTests.java | 8 +- ...xchangeFilterFunctionIntegrationTests.java | 8 +- ...adBalancerExchangeFilterFunctionTests.java | 3 +- .../BlockingLoadBalancedRetryFactory.java | 11 +- ...ngLoadBalancerClientAutoConfiguration.java | 7 +- .../config/LoadBalancerAutoConfiguration.java | 8 +- .../support/LoadBalancerClientFactory.java | 22 ++- ...chingServiceInstanceListSupplierTests.java | 11 +- 22 files changed, 512 insertions(+), 86 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java new file mode 100644 index 000000000..8aed2eedf --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2021 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 java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor; +import org.springframework.boot.context.properties.bind.AbstractBindHandler; +import org.springframework.boot.context.properties.bind.BindContext; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; +import org.springframework.util.CollectionUtils; + +/** + * @author Oleg Zhurakousky + * @since 2.1 + * + */ +public class BindingHandlerAdvise implements ConfigurationPropertiesBindHandlerAdvisor { + + private final Map mappings; + + public BindingHandlerAdvise(Map additionalMappings) { + this.mappings = new LinkedHashMap<>(); + this.mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"), + ConfigurationPropertyName.of("spring.cloud.loadbalancer")); + if (!CollectionUtils.isEmpty(additionalMappings)) { + this.mappings.putAll(additionalMappings); + } + } + + @Override + public BindHandler apply(BindHandler bindHandler) { + + BindHandler handler = new AbstractBindHandler(bindHandler) { + @Override + public Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { + ConfigurationPropertyName defaultName = getDefaultName(name); + if (defaultName != null) { + BindResult result = context.getBinder().bind(defaultName, target); + if (result.isBound()) { + return target.withExistingValue(result.get()); + } + } + return bindHandler.onStart(name, target, context); + } + }; + return handler; + } + + private ConfigurationPropertyName getDefaultName(ConfigurationPropertyName name) { + for (Map.Entry mapping : this.mappings.entrySet()) { + ConfigurationPropertyName from = mapping.getKey(); + ConfigurationPropertyName to = mapping.getValue(); + if ((from.isAncestorOf(name) && name.getNumberOfElements() > from.getNumberOfElements())) { + ConfigurationPropertyName defaultName = to; + for (int i = from.getNumberOfElements() + 1; i < name.getNumberOfElements(); i++) { + defaultName = defaultName.append(name.getElement(i, Form.UNIFORM)); + } + return defaultName; + } + } + return null; + } + + /** + * Provides mappings including the default mappings. + */ + public interface MappingsProvider { + + Map getDefaultMappings(); + + } + +} 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 ccafa07ee..403b80975 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 @@ -51,7 +51,7 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) -@EnableConfigurationProperties(LoadBalancerProperties.class) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientsProperties.class }) public class LoadBalancerAutoConfiguration { @LoadBalanced @@ -147,11 +147,10 @@ public static class RetryInterceptorAutoConfiguration { @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient, - LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory, - LoadBalancedRetryFactory loadBalancedRetryFactory, + LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory, ReactiveLoadBalancer.Factory loadBalancerFactory) { - return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory, - loadBalancedRetryFactory, loadBalancerFactory); + return new RetryLoadBalancerInterceptor(loadBalancerClient, requestFactory, loadBalancedRetryFactory, + loadBalancerFactory); } @Bean diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java new file mode 100644 index 000000000..5b4443aaa --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java @@ -0,0 +1,168 @@ +/* + * Copyright 2013-2021 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 java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Spencer Gibb + * @since 3.1.0 + */ +@ConfigurationProperties("spring.cloud.loadbalancer.clients") +public class LoadBalancerClientsProperties implements Map { + + Map clients = new HashMap<>(); + + @Override + public int size() { + return clients.size(); + } + + @Override + public boolean isEmpty() { + return clients.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return clients.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return clients.containsValue(value); + } + + @Override + public LoadBalancerProperties get(Object key) { + return clients.get(key); + } + + @Override + public LoadBalancerProperties put(String key, LoadBalancerProperties value) { + return clients.put(key, value); + } + + @Override + public LoadBalancerProperties remove(Object key) { + return clients.remove(key); + } + + @Override + public void putAll(Map m) { + clients.putAll(m); + } + + @Override + public void clear() { + clients.clear(); + } + + @Override + public Set keySet() { + return clients.keySet(); + } + + @Override + public Collection values() { + return clients.values(); + } + + @Override + public Set> entrySet() { + return clients.entrySet(); + } + + @Override + public boolean equals(Object o) { + return clients.equals(o); + } + + @Override + public int hashCode() { + return clients.hashCode(); + } + + @Override + public LoadBalancerProperties getOrDefault(Object key, LoadBalancerProperties defaultValue) { + return clients.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + clients.forEach(action); + } + + @Override + public void replaceAll( + BiFunction function) { + clients.replaceAll(function); + } + + @Override + public LoadBalancerProperties putIfAbsent(String key, LoadBalancerProperties value) { + return clients.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return clients.remove(key, value); + } + + @Override + public boolean replace(String key, LoadBalancerProperties oldValue, LoadBalancerProperties newValue) { + return clients.replace(key, oldValue, newValue); + } + + @Override + public LoadBalancerProperties replace(String key, LoadBalancerProperties value) { + return clients.replace(key, value); + } + + @Override + public LoadBalancerProperties computeIfAbsent(String key, + Function mappingFunction) { + return clients.computeIfAbsent(key, mappingFunction); + } + + @Override + public LoadBalancerProperties computeIfPresent(String key, + BiFunction remappingFunction) { + return clients.computeIfPresent(key, remappingFunction); + } + + @Override + public LoadBalancerProperties compute(String key, + BiFunction remappingFunction) { + return clients.compute(key, remappingFunction); + } + + @Override + public LoadBalancerProperties merge(String key, LoadBalancerProperties value, + BiFunction remappingFunction) { + return clients.merge(key, value, remappingFunction); + } + +} 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 index e56922b72..fc20c0557 100644 --- 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 @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.URI; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -50,19 +51,26 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto private final LoadBalancerClient loadBalancer; - private final LoadBalancerProperties properties; - private final LoadBalancerRequestFactory requestFactory; private final LoadBalancedRetryFactory lbRetryFactory; private final ReactiveLoadBalancer.Factory loadBalancerFactory; + @Deprecated public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory, ReactiveLoadBalancer.Factory loadBalancerFactory) { this.loadBalancer = loadBalancer; - this.properties = properties; + this.requestFactory = requestFactory; + this.lbRetryFactory = lbRetryFactory; + this.loadBalancerFactory = loadBalancerFactory; + } + + public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory, + LoadBalancedRetryFactory lbRetryFactory, + ReactiveLoadBalancer.Factory loadBalancerFactory) { + this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; this.lbRetryFactory = lbRetryFactory; this.loadBalancerFactory = loadBalancerFactory; @@ -159,14 +167,17 @@ private RetryTemplate createRetryTemplate(String serviceName, HttpRequest reques if (retryListeners != null && retryListeners.length != 0) { template.setListeners(retryListeners); } - template.setRetryPolicy(!properties.getRetry().isEnabled() || retryPolicy == null ? new NeverRetryPolicy() - : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName)); + template.setRetryPolicy( + !loadBalancerFactory.getProperties(serviceName).getRetry().isEnabled() || retryPolicy == null + ? new NeverRetryPolicy() + : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName)); return template; } private String getHint(String serviceId) { - String defaultHint = properties.getHint().getOrDefault("default", "default"); - String hintPropertyValue = properties.getHint().get(serviceId); + Map hint = loadBalancerFactory.getProperties(serviceId).getHint(); + String defaultHint = hint.getOrDefault("default", "default"); + String hintPropertyValue = hint.get(serviceId); return hintPropertyValue != null ? hintPropertyValue : defaultHint; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java index 0d2edad87..0da54842f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java @@ -16,6 +16,7 @@ package org.springframework.cloud.client.loadbalancer.reactive; +import org.springframework.cglib.core.internal.Function; import org.springframework.http.HttpMethod; /** @@ -55,4 +56,8 @@ public interface LoadBalancerRetryPolicy { */ boolean canRetryOnMethod(HttpMethod method); + interface Factory extends Function { + + } + } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java index 9f762ca2a..9d4613f85 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java @@ -22,6 +22,7 @@ import org.springframework.cloud.client.loadbalancer.DefaultRequest; import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; @@ -53,6 +54,10 @@ default Publisher> choose() { // conflicting name interface Factory { + default LoadBalancerProperties getProperties(String serviceId) { + return null; + } + ReactiveLoadBalancer getInstance(String serviceId); /** 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 42135a3cd..b1a93041c 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 @@ -17,18 +17,25 @@ package org.springframework.cloud.client.loadbalancer.reactive; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.ObjectProvider; 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.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.BindingHandlerAdvise; import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.function.client.WebClient; /** @@ -42,16 +49,29 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebClient.class) @ConditionalOnBean(ReactiveLoadBalancer.Factory.class) +@EnableConfigurationProperties({ LoadBalancerClientsProperties.class }) public class ReactorLoadBalancerClientAutoConfiguration { + @Bean + public BindingHandlerAdvise BindingHandlerAdvise(@Nullable BindingHandlerAdvise.MappingsProvider[] providers) { + Map additionalMappings = new HashMap<>(); + if (!ObjectUtils.isEmpty(providers)) { + for (int i = 0; i < providers.length; i++) { + BindingHandlerAdvise.MappingsProvider mappingsProvider = providers[i]; + additionalMappings.putAll(mappingsProvider.getDefaultMappings()); + } + } + return new BindingHandlerAdvise(additionalMappings); + } + @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false", matchIfMissing = true) @Bean public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction( - ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, + ReactiveLoadBalancer.Factory loadBalancerFactory, ObjectProvider> transformers) { - return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties, + return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, transformers.getIfAvailable(Collections::emptyList)); } @@ -59,18 +79,19 @@ public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunct @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true") @Bean public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction( - ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, - LoadBalancerRetryPolicy retryPolicy, + ReactiveLoadBalancer.Factory loadBalancerFactory, + LoadBalancerRetryPolicy.Factory retryPolicyFactory, ObjectProvider> transformers) { - return new RetryableLoadBalancerExchangeFilterFunction(retryPolicy, loadBalancerFactory, properties, + return new RetryableLoadBalancerExchangeFilterFunction(retryPolicyFactory, loadBalancerFactory, transformers.getIfAvailable(Collections::emptyList)); } @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true") @Bean - public LoadBalancerRetryPolicy loadBalancerRetryPolicy(LoadBalancerProperties properties) { - return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties); + public LoadBalancerRetryPolicy.Factory loadBalancerRetryPolicy( + ReactiveLoadBalancer.Factory loadBalancerFactory) { + return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.Factory(loadBalancerFactory); } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java index d5735b66d..528caffa8 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java @@ -61,8 +61,6 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx private final ReactiveLoadBalancer.Factory loadBalancerFactory; - private final LoadBalancerProperties properties; - private final List transformers; /** @@ -77,10 +75,16 @@ public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, List transformers) { this.loadBalancerFactory = loadBalancerFactory; - this.properties = properties; + this.transformers = transformers; + } + + public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory loadBalancerFactory, + List transformers) { + this.loadBalancerFactory = loadBalancerFactory; this.transformers = transformers; } @@ -99,7 +103,7 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction .getSupportedLifecycleProcessors( loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class); - String hint = getHint(serviceId, properties.getHint()); + String hint = getHint(serviceId, loadBalancerFactory.getProperties(serviceId).getHint()); RequestData requestData = new RequestData(clientRequest); DefaultRequest lbRequest = new DefaultRequest<>(new RequestDataContext(requestData, hint)); supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); @@ -120,7 +124,8 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction LOG.debug(String.format("LoadBalancer has retrieved the instance for service %s: %s", serviceId, instance.getUri())); } - LoadBalancerProperties.StickySession stickySessionProperties = properties.getStickySession(); + LoadBalancerProperties.StickySession stickySessionProperties = loadBalancerFactory.getProperties(serviceId) + .getStickySession(); ClientRequest newRequest = buildClientRequest(clientRequest, instance, stickySessionProperties.getInstanceIdCookieName(), stickySessionProperties.isAddServiceInstanceCookie(), transformers); diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.java index e04e0ede6..1a1eb6e5e 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.java @@ -16,6 +16,7 @@ package org.springframework.cloud.client.loadbalancer.reactive; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.http.HttpMethod; @@ -53,4 +54,20 @@ public boolean canRetryOnMethod(HttpMethod method) { return HttpMethod.GET.equals(method) || properties.getRetry().isRetryOnAllOperations(); } + static class Factory implements LoadBalancerRetryPolicy.Factory { + + final ReactiveLoadBalancer.Factory loadBalancerFactory; + + Factory(ReactiveLoadBalancer.Factory loadBalancerFactory) { + this.loadBalancerFactory = loadBalancerFactory; + } + + @Override + public LoadBalancerRetryPolicy apply(String serviceId) { + return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( + loadBalancerFactory.getProperties(serviceId)); + } + + } + } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java index 288c719a3..5256284ea 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java @@ -69,9 +69,7 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced private static final List> exceptions = Arrays.asList(IOException.class, TimeoutException.class, RetryableStatusCodeException.class); - private final LoadBalancerRetryPolicy retryPolicy; - - private final LoadBalancerProperties properties; + private final LoadBalancerRetryPolicy.Factory retryPolicyFactory; private final ReactiveLoadBalancer.Factory loadBalancerFactory; @@ -87,22 +85,26 @@ public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retry this(retryPolicy, loadBalancerFactory, properties, Collections.emptyList()); } + @Deprecated public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retryPolicy, ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, List transformers) { - this.retryPolicy = retryPolicy; + this.retryPolicyFactory = s -> retryPolicy; + this.loadBalancerFactory = loadBalancerFactory; + this.transformers = transformers; + } + + public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy.Factory retryPolicyFactory, + ReactiveLoadBalancer.Factory loadBalancerFactory, + List transformers) { + this.retryPolicyFactory = retryPolicyFactory; this.loadBalancerFactory = loadBalancerFactory; - this.properties = properties; this.transformers = transformers; } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Mono filter(ClientRequest clientRequest, ExchangeFunction next) { - LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest); - Retry exchangeRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnSameServiceInstance(), true); - Retry filterRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnNextServiceInstance(), false); - URI originalUrl = clientRequest.url(); String serviceId = originalUrl.getHost(); if (serviceId == null) { @@ -112,6 +114,15 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction } return Mono.just(ClientResponse.create(HttpStatus.BAD_REQUEST).body(message).build()); } + LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest); + LoadBalancerProperties properties = loadBalancerFactory.getProperties(serviceId); + + Retry exchangeRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnSameServiceInstance(), true, + properties.getRetry()); + Retry filterRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnNextServiceInstance(), false, + properties.getRetry()); + LoadBalancerRetryPolicy retryPolicy = retryPolicyFactory.apply(serviceId); + Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator .getSupportedLifecycleProcessors( loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class), @@ -154,7 +165,7 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) .map(clientResponse -> { loadBalancerRetryContext.setClientResponse(clientResponse); - if (shouldRetrySameServiceInstance(loadBalancerRetryContext)) { + if (shouldRetrySameServiceInstance(retryPolicy, loadBalancerRetryContext)) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", clientResponse.statusCode().value())); @@ -166,7 +177,7 @@ lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) }); }).map(clientResponse -> { loadBalancerRetryContext.setClientResponse(clientResponse); - if (shouldRetryNextServiceInstance(loadBalancerRetryContext)) { + if (shouldRetryNextServiceInstance(retryPolicy, loadBalancerRetryContext)) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", clientResponse.statusCode().value())); } @@ -177,8 +188,11 @@ lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) }).retryWhen(exchangeRetry)).retryWhen(filterRetry); } - private Retry buildRetrySpec(int max, boolean transientErrors) { - LoadBalancerProperties.Retry.Backoff backoffProperties = properties.getRetry().getBackoff(); + private Retry buildRetrySpec(int max, boolean transientErrors, LoadBalancerProperties.Retry retry) { + if (!retry.isEnabled()) { + return Retry.max(0).filter(this::isRetryException).transientErrors(transientErrors); + } + LoadBalancerProperties.Retry.Backoff backoffProperties = retry.getBackoff(); if (backoffProperties.isEnabled()) { return RetrySpec.backoff(max, backoffProperties.getMinBackoff()).filter(this::isRetryException) .maxBackoff(backoffProperties.getMaxBackoff()).jitter(backoffProperties.getJitter()) @@ -187,7 +201,8 @@ private Retry buildRetrySpec(int max, boolean transientErrors) { return RetrySpec.max(max).filter(this::isRetryException).transientErrors(transientErrors); } - private boolean shouldRetrySameServiceInstance(LoadBalancerRetryContext loadBalancerRetryContext) { + private boolean shouldRetrySameServiceInstance(LoadBalancerRetryPolicy retryPolicy, + LoadBalancerRetryContext loadBalancerRetryContext) { boolean shouldRetry = retryPolicy.retryableStatusCode(loadBalancerRetryContext.getResponseStatusCode()) && retryPolicy.canRetryOnMethod(loadBalancerRetryContext.getRequestMethod()) && retryPolicy.canRetrySameServiceInstance(loadBalancerRetryContext); @@ -197,7 +212,8 @@ private boolean shouldRetrySameServiceInstance(LoadBalancerRetryContext loadBala return shouldRetry; } - private boolean shouldRetryNextServiceInstance(LoadBalancerRetryContext loadBalancerRetryContext) { + private boolean shouldRetryNextServiceInstance(LoadBalancerRetryPolicy retryPolicy, + LoadBalancerRetryContext loadBalancerRetryContext) { boolean shouldRetry = retryPolicy.retryableStatusCode(loadBalancerRetryContext.getResponseStatusCode()) && retryPolicy.canRetryOnMethod(loadBalancerRetryContext.getRequestMethod()) && retryPolicy.canRetryNextServiceInstance(loadBalancerRetryContext); 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 4e64b044a..1a0d21b8d 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 @@ -103,8 +103,8 @@ LoadBalancerClient loadBalancerClient() { } @Bean - ReactiveLoadBalancer.Factory loadBalancerFactory() { - return new TestLoadBalancerFactory(); + ReactiveLoadBalancer.Factory loadBalancerFactory(LoadBalancerProperties properties) { + return new TestLoadBalancerFactory(properties); } } @@ -176,6 +176,12 @@ public URI reconstructURI(ServiceInstance instance, URI original) { private static class TestLoadBalancerFactory implements ReactiveLoadBalancer.Factory { + private final LoadBalancerProperties properties; + + TestLoadBalancerFactory(LoadBalancerProperties properties) { + this.properties = properties; + } + @Override public ReactiveLoadBalancer getInstance(String serviceId) { throw new UnsupportedOperationException("Not implemented."); @@ -191,6 +197,11 @@ public Map getInstances(String name, Class type) { throw new UnsupportedOperationException("Not implemented."); } + @Override + public LoadBalancerProperties getProperties(String serviceId) { + return properties; + } + } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java index 94ad3f8e8..579a25bcf 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java @@ -62,6 +62,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; /** * @author Ryan Baxter @@ -88,7 +89,8 @@ public void setUp() { client = mock(LoadBalancerClient.class); lbRequestFactory = mock(LoadBalancerRequestFactory.class); properties = new LoadBalancerProperties(); - lbFactory = mock(ReactiveLoadBalancer.Factory.class); + lbFactory = mock(ReactiveLoadBalancer.Factory.class, withSettings().lenient()); + when(lbFactory.getProperties(any())).thenReturn(properties); } @AfterEach @@ -105,8 +107,8 @@ public void interceptDisableRetry() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()); properties.getRetry().setEnabled(false); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + loadBalancedRetryFactory, lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -123,8 +125,8 @@ public void interceptInvalidHost() throws Throwable { HttpRequest request = mock(HttpRequest.class); when(request.getURI()).thenReturn(new URI("http://foo_underscore")); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + loadBalancedRetryFactory, lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); Assertions.assertThrows(IllegalStateException.class, () -> { @@ -143,8 +145,8 @@ public void interceptNeverRetry() throws Throwable { .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + loadBalancedRetryFactory, lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); interceptor.intercept(request, body, execution); @@ -163,8 +165,8 @@ public void interceptSuccess() throws Throwable { .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy), lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -189,8 +191,8 @@ public void interceptRetryOnStatusCode() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), nullable(LoadBalancerRequest.class))) .thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy), lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -222,8 +224,8 @@ public void interceptRetryFailOnStatusCode() throws Throwable { properties.getRetry().setEnabled(true); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy), lbFactory); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); verify(client, times(1)).execute(eq("foo"), eq(serviceInstance), @@ -252,8 +254,8 @@ public void interceptRetry() throws Throwable { .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -276,8 +278,8 @@ public void interceptFailedRetry() throws Exception { .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy), lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); Assertions.assertThrows(IOException.class, () -> { @@ -305,8 +307,7 @@ public void retryListenerTest() throws Throwable { properties.getRetry().setEnabled(true); MyRetryListener retryListener = new MyRetryListener(); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { retryListener }), lbFactory); byte[] body = new byte[] {}; @@ -333,8 +334,8 @@ public void retryWithDefaultConstructorTest() throws Throwable { .thenThrow(new IOException()).thenReturn(clientHttpResponse); properties.getRetry().setEnabled(true); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -357,8 +358,7 @@ public boolean open(RetryContext context, RetryCallback return false; } }; - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { myRetryListener }), lbFactory); ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -376,8 +376,8 @@ public void shouldNotDuplicateLifecycleCalls() throws IOException, URISyntaxExce HttpRequest request = mock(HttpRequest.class); when(request.getURI()).thenReturn(new URI("http://test")); TestLoadBalancerClient client = new TestLoadBalancerClient(); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbRequestFactory, + loadBalancedRetryFactory, lbFactory); interceptor.intercept(request, new byte[] {}, mock(ClientHttpRequestExecution.class)); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java index 5e4b27f90..57a7e5d9a 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java @@ -63,6 +63,7 @@ class LoadBalancerClientRequestTransformerTest { @BeforeEach void setUp() { when(factory.getInstance("testServiceId")).thenReturn(new TestReactiveLoadBalancer()); + when(factory.getProperties(any())).thenReturn(properties); when(clientRequest.method()).thenReturn(HttpMethod.GET); when(clientRequest.url()).thenReturn(URI.create("http://testServiceId")); when(clientRequest.headers()).thenReturn(new HttpHeaders()); @@ -75,7 +76,7 @@ void setUp() { void transformReactorLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); ReactorLoadBalancerExchangeFilterFunction filterFunction = new ReactorLoadBalancerExchangeFilterFunction( - factory, properties, Arrays.asList(new Transformer1(), new Transformer2())); + factory, Arrays.asList(new Transformer1(), new Transformer2())); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); @@ -87,7 +88,7 @@ void transformReactorLoadBalancerExchangeFilterFunction() { void transformRetryableLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Arrays.asList(new Transformer1(), new Transformer2())); + s -> policy, factory, Arrays.asList(new Transformer1(), new Transformer2())); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); 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 b5ffe50ca..376faa336 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 @@ -16,14 +16,19 @@ package org.springframework.cloud.client.loadbalancer.reactive; +import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; @@ -128,6 +133,24 @@ void noCustomWebClientBuilders() { then(getFilters(builder)).isNullOrEmpty(); } + @Test + void defaultPropertiesWorks() { + ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE) + .sources(NoWebClientBuilder.class, ReactorLoadBalancerClientAutoConfiguration.class, + LoadBalancerBeanPostProcessorAutoConfiguration.class, WebClientAutoConfiguration.class) + .properties("spring.cloud.loadbalancer.health-check.initial-delay=1s", + "spring.cloud.loadbalancer.clients.myclient.health-check.interval=30s") + .run(); + LoadBalancerClientsProperties clientsProperties = context.getBean(LoadBalancerClientsProperties.class); + + then(clientsProperties).containsKey("myclient"); + LoadBalancerProperties properties = clientsProperties.get("myclient"); + // default value + then(properties.getHealthCheck().getInitialDelay()).isEqualTo(Duration.ofSeconds(1)); + // client specific value + then(properties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30)); + } + private ConfigurableApplicationContext init(Class config) { return LoadBalancerTestUtils.init(config, ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class); @@ -137,7 +160,7 @@ private ConfigurableApplicationContext init(Class config) { protected static class NoWebClientBuilder { @Bean - ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory() { + ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(LoadBalancerProperties properties) { return new ReactiveLoadBalancer.Factory() { @Override public ReactiveLoadBalancer getInstance(String serviceId) { @@ -153,6 +176,11 @@ public Map getInstances(String name, Class type) { public X getInstance(String name, Class clazz, Class... generics) { throw new UnsupportedOperationException("Not implemented."); } + + @Override + public LoadBalancerProperties getProperties(String serviceId) { + return properties; + } }; } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java index 963403d6f..73b9f6e34 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java @@ -171,7 +171,8 @@ String callbackTestResult() { } @Bean - ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient) { + ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient, + LoadBalancerProperties properties) { return new ReactiveLoadBalancer.Factory() { private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle(); @@ -198,6 +199,11 @@ public Map getInstances(String name, Class type) { public X getInstance(String name, Class clazz, Class... generics) { return null; } + + @Override + public LoadBalancerProperties getProperties(String serviceId) { + return properties; + } }; } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java index a1c3eb3ca..bb8945bbf 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -228,7 +228,8 @@ String exception() { } @Bean - ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient) { + ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient, + LoadBalancerProperties properties) { return new ReactiveLoadBalancer.Factory() { private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle(); @@ -256,6 +257,11 @@ public Map getInstances(String name, Class type) { public X getInstance(String name, Class clazz, Class... generics) { return null; } + + @Override + public LoadBalancerProperties getProperties(String serviceId) { + return properties; + } }; } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java index 5a183c8d0..d78395d07 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java @@ -74,6 +74,7 @@ void setUp() { properties.getRetry().getRetryableStatusCodes().add(404); when(clientRequest.url()).thenReturn(URI.create("http://test")); when(factory.getInstance("test")).thenReturn(new TestReactiveLoadBalancer()); + when(factory.getProperties(any())).thenReturn(properties); when(clientRequest.headers()).thenReturn(new HttpHeaders()); when(clientRequest.cookies()).thenReturn(new HttpHeaders()); @@ -138,7 +139,7 @@ void shouldRetryOnMethodOtherThanGetWhenEnabled() { properties.getRetry().getRetryableStatusCodes().add(404); LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Collections.emptyList()); + s -> policy, factory, Collections.emptyList()); when(clientRequest.method()).thenReturn(HttpMethod.POST); when(clientResponse.statusCode()).thenReturn(HttpStatus.NOT_FOUND); when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java index 92a063756..6a8bc07c1 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java @@ -16,10 +16,11 @@ package org.springframework.cloud.loadbalancer.blocking.retry; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; /** @@ -31,15 +32,15 @@ */ public class BlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory { - private final LoadBalancerProperties loadBalancerProperties; + private final ReactiveLoadBalancer.Factory loadBalancerFactory; - public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties) { - this.loadBalancerProperties = loadBalancerProperties; + public BlockingLoadBalancedRetryFactory(ReactiveLoadBalancer.Factory loadBalancerFactory) { + this.loadBalancerFactory = loadBalancerFactory; } @Override public LoadBalancedRetryPolicy createRetryPolicy(String serviceId, ServiceInstanceChooser serviceInstanceChooser) { - return new BlockingLoadBalancedRetryPolicy(loadBalancerProperties); + return new BlockingLoadBalancedRetryPolicy(loadBalancerFactory.getProperties(serviceId)); } } 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 f9fad6065..43aa836e7 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 @@ -23,10 +23,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; @@ -75,8 +77,9 @@ protected static class BlockingLoadBalancerRetryConfig { @Bean @ConditionalOnMissingBean - LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) { - return new BlockingLoadBalancedRetryFactory(properties); + LoadBalancedRetryFactory loadBalancedRetryFactory( + ReactiveLoadBalancer.Factory loadBalancerFactory) { + return new BlockingLoadBalancedRetryFactory(loadBalancerFactory); } } 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 bdde78608..b966e5960 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 @@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration; @@ -40,7 +41,7 @@ */ @Configuration(proxyBeanMethods = false) @LoadBalancerClients -@EnableConfigurationProperties(LoadBalancerProperties.class) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientsProperties.class }) @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class }) @ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", havingValue = "true", matchIfMissing = true) @@ -60,8 +61,9 @@ public LoadBalancerZoneConfig zoneConfig(Environment environment) { @ConditionalOnMissingBean @Bean - public LoadBalancerClientFactory loadBalancerClientFactory() { - LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(); + public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerProperties properties, + LoadBalancerClientsProperties clientsProperties) { + LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties, clientsProperties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java index 518e1a981..67ea06879 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java @@ -17,6 +17,8 @@ package org.springframework.cloud.loadbalancer.support; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.context.named.NamedContextFactory; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration; @@ -46,8 +48,15 @@ public class LoadBalancerClientFactory extends NamedContextFactory getInstance(String serviceId) { return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class); } + @Override + public LoadBalancerProperties getProperties(String serviceId) { + if (!clientsProperties.containsKey(serviceId)) { + // no specific client properties, return default + return properties; + } + // because specifics are overlayed on top of defaults, everything in `properties`, + // unless overridden, is in `clientsProperties` + return clientsProperties.get(serviceId); + } + } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java index 91251b46e..bef42d298 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java @@ -26,6 +26,7 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; @@ -100,8 +101,9 @@ ReactorLoadBalancer reactorLoadBalancer(ObjectProvider Date: Thu, 21 Oct 2021 23:37:43 -0400 Subject: [PATCH 2/8] Moves DefaultsBindHandlerAdvisor to commons. Adds LoadBalancerDefaultMappingsProviderAutoConfiguration to supply the loadbalancer defaults mapping to DefaultsBindHandlerAdvisor --- ...aultMappingsProviderAutoConfiguration.java | 46 ++++++++++++++++++ ...orLoadBalancerClientAutoConfiguration.java | 18 ------- ...igDataMissingEnvironmentPostProcessor.java | 1 + .../CommonsConfigAutoConfiguration.java | 48 +++++++++++++++++++ .../config/DefaultsBindHandlerAdvisor.java} | 8 ++-- .../main/resources/META-INF/spring.factories | 4 +- ...dBalancerClientAutoConfigurationTests.java | 11 +++-- 7 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/CommonsConfigAutoConfiguration.java rename spring-cloud-commons/src/main/java/org/springframework/cloud/{client/loadbalancer/BindingHandlerAdvise.java => commons/config/DefaultsBindHandlerAdvisor.java} (88%) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java new file mode 100644 index 000000000..28d36e9d5 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2020 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 java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.cloud.commons.config.DefaultsBindHandlerAdvisor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + * @since 3.1.0 + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureBefore(name = "org.springframework.cloud.commons.config.CommonsConfigAutoConfiguration") +public class LoadBalancerDefaultMappingsProviderAutoConfiguration { + + @Bean + public DefaultsBindHandlerAdvisor.MappingsProvider rabbitExtendedPropertiesDefaultMappingsProvider() { + return () -> { + Map mappings = new HashMap<>(); + mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"), + ConfigurationPropertyName.of("spring.cloud.loadbalancer")); + return mappings; + }; + } + +} 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 b1a93041c..47870ab35 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 @@ -17,9 +17,7 @@ package org.springframework.cloud.client.loadbalancer.reactive; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -27,15 +25,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.BindingHandlerAdvise; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.lang.Nullable; -import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.function.client.WebClient; /** @@ -52,18 +46,6 @@ @EnableConfigurationProperties({ LoadBalancerClientsProperties.class }) public class ReactorLoadBalancerClientAutoConfiguration { - @Bean - public BindingHandlerAdvise BindingHandlerAdvise(@Nullable BindingHandlerAdvise.MappingsProvider[] providers) { - Map additionalMappings = new HashMap<>(); - if (!ObjectUtils.isEmpty(providers)) { - for (int i = 0; i < providers.length; i++) { - BindingHandlerAdvise.MappingsProvider mappingsProvider = providers[i]; - additionalMappings.putAll(mappingsProvider.getDefaultMappings()); - } - } - return new BindingHandlerAdvise(additionalMappings); - } - @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false", matchIfMissing = true) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java index dc7a3b99b..8d204d0a0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/ConfigDataMissingEnvironmentPostProcessor.java @@ -43,6 +43,7 @@ /** * @author Ryan Baxter */ +// TODO: 4.0.0 move to org.springframework.cloud.commons.config public abstract class ConfigDataMissingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { /** diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/CommonsConfigAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/CommonsConfigAutoConfiguration.java new file mode 100644 index 000000000..95647158b --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/CommonsConfigAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2020 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.commons.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * @author Spencer Gibb + * @since 3.1.0 + */ +@Configuration(proxyBeanMethods = false) +public class CommonsConfigAutoConfiguration { + + @Bean + public DefaultsBindHandlerAdvisor defaultsBindHandlerAdvisor( + @Nullable DefaultsBindHandlerAdvisor.MappingsProvider[] providers) { + Map additionalMappings = new HashMap<>(); + if (!ObjectUtils.isEmpty(providers)) { + for (int i = 0; i < providers.length; i++) { + DefaultsBindHandlerAdvisor.MappingsProvider mappingsProvider = providers[i]; + additionalMappings.putAll(mappingsProvider.getDefaultMappings()); + } + } + return new DefaultsBindHandlerAdvisor(additionalMappings); + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java similarity index 88% rename from spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java rename to spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java index 8aed2eedf..4f2a20ba7 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/BindingHandlerAdvise.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.client.loadbalancer; +package org.springframework.cloud.commons.config; import java.util.LinkedHashMap; import java.util.Map; @@ -34,14 +34,12 @@ * @since 2.1 * */ -public class BindingHandlerAdvise implements ConfigurationPropertiesBindHandlerAdvisor { +public class DefaultsBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor { private final Map mappings; - public BindingHandlerAdvise(Map additionalMappings) { + public DefaultsBindHandlerAdvisor(Map additionalMappings) { this.mappings = new LinkedHashMap<>(); - this.mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"), - ConfigurationPropertyName.of("spring.cloud.loadbalancer")); if (!CollectionUtils.isEmpty(additionalMappings)) { this.mappings.putAll(additionalMappings); } 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 016abdd69..81723a805 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories @@ -9,6 +9,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.LoadBalancerDefaultMappingsProviderAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\ org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\ org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\ @@ -16,7 +17,8 @@ org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\ org.springframework.cloud.commons.util.UtilAutoConfiguration,\ org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\ org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration,\ -org.springframework.cloud.commons.security.ResourceServerTokenRelayAutoConfiguration +org.springframework.cloud.commons.security.ResourceServerTokenRelayAutoConfiguration,\ +org.springframework.cloud.commons.config.CommonsConfigAutoConfiguration # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.client.HostInfoEnvironmentPostProcessor 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 376faa336..286075907 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 @@ -23,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.WebApplicationType; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; @@ -136,8 +136,7 @@ void noCustomWebClientBuilders() { @Test void defaultPropertiesWorks() { ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .sources(NoWebClientBuilder.class, ReactorLoadBalancerClientAutoConfiguration.class, - LoadBalancerBeanPostProcessorAutoConfiguration.class, WebClientAutoConfiguration.class) + .sources(OneWebClientBuilder.class, DefaulConfig.class) .properties("spring.cloud.loadbalancer.health-check.initial-delay=1s", "spring.cloud.loadbalancer.clients.myclient.health-check.interval=30s") .run(); @@ -156,6 +155,12 @@ private ConfigurableApplicationContext init(Class config) { LoadBalancerBeanPostProcessorAutoConfiguration.class); } + @Configuration + @EnableAutoConfiguration + protected static class DefaulConfig { + + } + @Configuration protected static class NoWebClientBuilder { From a9ec5354bf75b1eb0951fcde22e005975c1cd243 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 22 Oct 2021 11:18:16 -0400 Subject: [PATCH 3/8] Remove rabbit name from copypasta --- .../LoadBalancerDefaultMappingsProviderAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java index 28d36e9d5..501f1c4bf 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerDefaultMappingsProviderAutoConfiguration.java @@ -34,7 +34,7 @@ public class LoadBalancerDefaultMappingsProviderAutoConfiguration { @Bean - public DefaultsBindHandlerAdvisor.MappingsProvider rabbitExtendedPropertiesDefaultMappingsProvider() { + public DefaultsBindHandlerAdvisor.MappingsProvider loadBalancerClientsDefaultsMappingsProvider() { return () -> { Map mappings = new HashMap<>(); mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"), From 0c69d70e5118e005eb5531ec43651254db40f5e8 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 26 Oct 2021 13:45:46 -0400 Subject: [PATCH 4/8] Rearranges spring.cloud.loadbalancer.client. LoadBalancerClientsProperties now extends LoadBalancerProperties. The `clients` attribute is now a field. The `@ConfigurationProperties("spring.cloud.loadbalancer")` has moved from LoadBalancerProperties to LoadBalancerClientsProperties --- .../LoadBalancerAutoConfiguration.java | 2 +- .../LoadBalancerClientsProperties.java | 141 +----------------- .../loadbalancer/LoadBalancerProperties.java | 1 - ...orLoadBalancerClientAutoConfiguration.java | 2 +- ...dBalancerClientAutoConfigurationTests.java | 15 +- ...adBalancerExchangeFilterFunctionTests.java | 5 - ...xchangeFilterFunctionIntegrationTests.java | 5 - ...ngLoadBalancerClientAutoConfiguration.java | 3 +- .../config/LoadBalancerAutoConfiguration.java | 8 +- .../support/LoadBalancerClientFactory.java | 12 +- ...chingServiceInstanceListSupplierTests.java | 10 +- 11 files changed, 23 insertions(+), 181 deletions(-) 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 403b80975..7fdc57da5 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 @@ -51,7 +51,7 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) -@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientsProperties.class }) +@EnableConfigurationProperties(LoadBalancerClientsProperties.class) public class LoadBalancerAutoConfiguration { @LoadBalanced diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java index 5b4443aaa..919e955fa 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java @@ -16,13 +16,8 @@ package org.springframework.cloud.client.loadbalancer; -import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -30,139 +25,13 @@ * @author Spencer Gibb * @since 3.1.0 */ -@ConfigurationProperties("spring.cloud.loadbalancer.clients") -public class LoadBalancerClientsProperties implements Map { +@ConfigurationProperties("spring.cloud.loadbalancer") +public class LoadBalancerClientsProperties extends LoadBalancerProperties { - Map clients = new HashMap<>(); + private Map clients = new HashMap<>(); - @Override - public int size() { - return clients.size(); - } - - @Override - public boolean isEmpty() { - return clients.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return clients.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return clients.containsValue(value); - } - - @Override - public LoadBalancerProperties get(Object key) { - return clients.get(key); - } - - @Override - public LoadBalancerProperties put(String key, LoadBalancerProperties value) { - return clients.put(key, value); - } - - @Override - public LoadBalancerProperties remove(Object key) { - return clients.remove(key); - } - - @Override - public void putAll(Map m) { - clients.putAll(m); - } - - @Override - public void clear() { - clients.clear(); - } - - @Override - public Set keySet() { - return clients.keySet(); - } - - @Override - public Collection values() { - return clients.values(); - } - - @Override - public Set> entrySet() { - return clients.entrySet(); - } - - @Override - public boolean equals(Object o) { - return clients.equals(o); - } - - @Override - public int hashCode() { - return clients.hashCode(); - } - - @Override - public LoadBalancerProperties getOrDefault(Object key, LoadBalancerProperties defaultValue) { - return clients.getOrDefault(key, defaultValue); - } - - @Override - public void forEach(BiConsumer action) { - clients.forEach(action); - } - - @Override - public void replaceAll( - BiFunction function) { - clients.replaceAll(function); - } - - @Override - public LoadBalancerProperties putIfAbsent(String key, LoadBalancerProperties value) { - return clients.putIfAbsent(key, value); - } - - @Override - public boolean remove(Object key, Object value) { - return clients.remove(key, value); - } - - @Override - public boolean replace(String key, LoadBalancerProperties oldValue, LoadBalancerProperties newValue) { - return clients.replace(key, oldValue, newValue); - } - - @Override - public LoadBalancerProperties replace(String key, LoadBalancerProperties value) { - return clients.replace(key, value); - } - - @Override - public LoadBalancerProperties computeIfAbsent(String key, - Function mappingFunction) { - return clients.computeIfAbsent(key, mappingFunction); - } - - @Override - public LoadBalancerProperties computeIfPresent(String key, - BiFunction remappingFunction) { - return clients.computeIfPresent(key, remappingFunction); - } - - @Override - public LoadBalancerProperties compute(String key, - BiFunction remappingFunction) { - return clients.compute(key, remappingFunction); - } - - @Override - public LoadBalancerProperties merge(String key, LoadBalancerProperties value, - BiFunction remappingFunction) { - return clients.merge(key, value, remappingFunction); + public Map getClients() { + return this.clients; } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index bbefb715d..2ddfc17df 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -34,7 +34,6 @@ * @author Olga Maciaszek-Sharma * @since 2.2.1 */ -@ConfigurationProperties("spring.cloud.loadbalancer") public class LoadBalancerProperties { /** 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 47870ab35..a132b73be 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 @@ -43,7 +43,7 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebClient.class) @ConditionalOnBean(ReactiveLoadBalancer.Factory.class) -@EnableConfigurationProperties({ LoadBalancerClientsProperties.class }) +@EnableConfigurationProperties(LoadBalancerClientsProperties.class) public class ReactorLoadBalancerClientAutoConfiguration { @ConditionalOnMissingBean 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 286075907..1e2eebcdb 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 @@ -140,14 +140,14 @@ void defaultPropertiesWorks() { .properties("spring.cloud.loadbalancer.health-check.initial-delay=1s", "spring.cloud.loadbalancer.clients.myclient.health-check.interval=30s") .run(); - LoadBalancerClientsProperties clientsProperties = context.getBean(LoadBalancerClientsProperties.class); + LoadBalancerClientsProperties properties = context.getBean(LoadBalancerClientsProperties.class); - then(clientsProperties).containsKey("myclient"); - LoadBalancerProperties properties = clientsProperties.get("myclient"); + then(properties.getClients()).containsKey("myclient"); + LoadBalancerProperties clientProperties = properties.getClients().get("myclient"); // default value - then(properties.getHealthCheck().getInitialDelay()).isEqualTo(Duration.ofSeconds(1)); + then(clientProperties.getHealthCheck().getInitialDelay()).isEqualTo(Duration.ofSeconds(1)); // client specific value - then(properties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30)); + then(clientProperties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30)); } private ConfigurableApplicationContext init(Class config) { @@ -195,11 +195,6 @@ LoadBalancedRetryFactory loadBalancedRetryFactory() { }; } - @Bean - LoadBalancerProperties loadBalancerProperties() { - return new LoadBalancerProperties(); - } - } @Configuration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java index 73b9f6e34..502db407c 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java @@ -207,11 +207,6 @@ public LoadBalancerProperties getProperties(String serviceId) { }; } - @Bean - LoadBalancerProperties loadBalancerProperties() { - return new LoadBalancerProperties(); - } - } protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java index bb8945bbf..0363e82fe 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -265,11 +265,6 @@ public LoadBalancerProperties getProperties(String serviceId) { }; } - @Bean - LoadBalancerProperties loadBalancerProperties() { - return new LoadBalancerProperties(); - } - @Bean RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction(LoadBalancerProperties properties, ReactiveLoadBalancer.Factory factory) { 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 43aa836e7..2da800e2f 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 @@ -27,6 +27,7 @@ import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; @@ -72,7 +73,7 @@ public LoadBalancerServiceInstanceCookieTransformer loadBalancerServiceInstanceC @Configuration @ConditionalOnClass(RetryTemplate.class) - @EnableConfigurationProperties(LoadBalancerProperties.class) + @EnableConfigurationProperties(LoadBalancerClientsProperties.class) protected static class BlockingLoadBalancerRetryConfig { @Bean 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 b966e5960..e3f7519d6 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 @@ -25,7 +25,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; @@ -41,7 +40,7 @@ */ @Configuration(proxyBeanMethods = false) @LoadBalancerClients -@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientsProperties.class }) +@EnableConfigurationProperties(LoadBalancerClientsProperties.class) @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class }) @ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", havingValue = "true", matchIfMissing = true) @@ -61,9 +60,8 @@ public LoadBalancerZoneConfig zoneConfig(Environment environment) { @ConditionalOnMissingBean @Bean - public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerProperties properties, - LoadBalancerClientsProperties clientsProperties) { - LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties, clientsProperties); + public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) { + LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java index 67ea06879..89e2bbed0 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java @@ -48,15 +48,11 @@ public class LoadBalancerClientFactory extends NamedContextFactory getInstance(String serviceId) { @Override public LoadBalancerProperties getProperties(String serviceId) { - if (!clientsProperties.containsKey(serviceId)) { + if (!properties.getClients().containsKey(serviceId)) { // no specific client properties, return default return properties; } // because specifics are overlayed on top of defaults, everything in `properties`, // unless overridden, is in `clientsProperties` - return clientsProperties.get(serviceId); + return properties.getClients().get(serviceId); } } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java index bef42d298..282aafa24 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java @@ -101,9 +101,8 @@ ReactorLoadBalancer reactorLoadBalancer(ObjectProvider Date: Tue, 26 Oct 2021 14:02:15 -0400 Subject: [PATCH 5/8] Updates javadoc for Loadbalancer config props. --- .../client/loadbalancer/LoadBalancerClientsProperties.java | 6 ++++++ .../cloud/client/loadbalancer/LoadBalancerProperties.java | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java index 919e955fa..240829850 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java @@ -22,6 +22,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties; /** + * A {@link ConfigurationProperties} bean for Spring Cloud Loadbalancer. + * + * Individual clients are configured via the {@link LoadBalancerClientsProperties#clients} + * field. Defaults and other properties are located in the {@link LoadBalancerProperties} + * base class. + * * @author Spencer Gibb * @since 3.1.0 */ diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index 2ddfc17df..644970c77 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -29,7 +29,10 @@ import org.springframework.util.LinkedCaseInsensitiveMap; /** - * A {@link ConfigurationProperties} bean for Spring Cloud LoadBalancer. + * The base configuration bean for Spring Cloud LoadBalancer. + * + * See {@link LoadBalancerClientsProperties} for the {@link ConfigurationProperties} + * annotation. * * @author Olga Maciaszek-Sharma * @since 2.2.1 From d6c739b49017ca04b9fb73d8a8c37b871e02d926 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 26 Oct 2021 15:42:14 -0400 Subject: [PATCH 6/8] Add deprecated empty constructor back. --- .../support/LoadBalancerClientFactory.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java index 89e2bbed0..d2cd44423 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/LoadBalancerClientFactory.java @@ -16,6 +16,9 @@ package org.springframework.cloud.loadbalancer.support; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; @@ -38,6 +41,8 @@ public class LoadBalancerClientFactory extends NamedContextFactory implements ReactiveLoadBalancer.Factory { + private static final Log log = LogFactory.getLog(LoadBalancerClientFactory.class); + /** * Property source name for load balancer. */ @@ -50,6 +55,11 @@ public class LoadBalancerClientFactory extends NamedContextFactory getInstance(String serviceId) { @Override public LoadBalancerProperties getProperties(String serviceId) { + if (properties == null) { + if (log.isWarnEnabled()) { + log.warn("LoadBalancerClientsProperties is null. Please use the new constructor."); + } + return null; + } if (!properties.getClients().containsKey(serviceId)) { // no specific client properties, return default return properties; From a71ebb7e10e8dfd4a52f7b2e3784a29f6fbccc80 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 26 Oct 2021 15:46:36 -0400 Subject: [PATCH 7/8] Updates since tag --- .../cloud/commons/config/DefaultsBindHandlerAdvisor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java index 4f2a20ba7..7afe89594 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/config/DefaultsBindHandlerAdvisor.java @@ -31,8 +31,7 @@ /** * @author Oleg Zhurakousky - * @since 2.1 - * + * @since 3.1.0 */ public class DefaultsBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor { From e684c3bbf7f2b3fb8483222d3973ea276885280d Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 26 Oct 2021 16:02:25 -0400 Subject: [PATCH 8/8] Added documentation --- .../main/asciidoc/spring-cloud-commons.adoc | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index ac6232d93..cb2cbf674 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -468,6 +468,8 @@ For the reactive implementation, you can additionally set: For the reactive implementation, you can also implement your own `LoadBalancerRetryPolicy` to have more detailed control over the load-balanced call retries. +NOTE: Individual Loadbalancer clients may be configured individually with the same properties as above except the prefix is `spring.cloud.loadbalancer.clients..*` where `clientId` is the name of the loadbalancer. + NOTE: For load-balanced retries, by default, we wrap the `ServiceInstanceListSupplier` bean with `RetryAwareServiceInstanceListSupplier` to select a different instance from the one previously chosen, if available. You can disable this behavior by setting the value of `spring.cloud.loadbalancer.retry.avoidPreviousInstance` to `false`. ==== @@ -1246,6 +1248,27 @@ NOTE: The meters are registered in the registry when at least one record is adde TIP: You can further configure the behavior of those metrics (for example, add https://micrometer.io/docs/concepts#_histograms_and_percentiles[publishing percentiles and histograms]) by https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-per-meter-properties[adding `MeterFilters`]. +=== Configuring Individual LoadBalancerClients + +Individual Loadbalancer clients may be configured individually with a different prefix `spring.cloud.loadbalancer.clients..*` where `clientId` is the name of the loadbalancer. Default configuration values may be set in the `spring.cloud.loadbalancer.*` namespace and will be merged with the client specific values taking precedence + +.application.yml +==== +---- +spring: + cloud: + loadbalancer: + health-check: + initial-delay: 1s + clients: + myclient: + health-check: + interval: 30s +---- +==== + +The above example will result in a merged health-check `@ConfigurationProperties` object with `initial-delay=1s` and `interval=30s`. + == Spring Cloud Circuit Breaker include::spring-cloud-circuitbreaker.adoc[leveloffset=+1]