Adds config per load balancer client. (#1028)

Client specific configuration goes under `spring.cloud.loadbalancer.clients.<serviceId>.*`. Defaults are set using `spring.cloud.loadbalancer.*`. 

Adds LoadBalancerDefaultMappingsProviderAutoConfiguration to supply the loadbalancer defaults mapping to DefaultsBindHandlerAdvisor

LoadBalancerClientsProperties now extends LoadBalancerProperties. The `clients` attribute is now a field. The `@ConfigurationProperties("spring.cloud.loadbalancer")` has moved from LoadBalancerProperties to LoadBalancerClientsProperties

Fixes gh-914
This commit is contained in:
Spencer Gibb
2021-10-26 16:03:00 -04:00
committed by GitHub
parent 95ce58741d
commit 3a6a9e0a91
28 changed files with 504 additions and 107 deletions

View File

@@ -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.<clientId>.*` 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.<clientId>.*` 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]

View File

@@ -51,7 +51,7 @@ import org.springframework.web.client.RestTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@@ -147,11 +147,10 @@ public class LoadBalancerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory,
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory,
loadBalancedRetryFactory, loadBalancerFactory);
return new RetryLoadBalancerInterceptor(loadBalancerClient, requestFactory, loadBalancedRetryFactory,
loadBalancerFactory);
}
@Bean

View File

@@ -0,0 +1,43 @@
/*
* 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.HashMap;
import java.util.Map;
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
*/
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerClientsProperties extends LoadBalancerProperties {
private Map<String, LoadBalancerProperties> clients = new HashMap<>();
public Map<String, LoadBalancerProperties> getClients() {
return this.clients;
}
}

View File

@@ -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 loadBalancerClientsDefaultsMappingsProvider() {
return () -> {
Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings = new HashMap<>();
mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"),
ConfigurationPropertyName.of("spring.cloud.loadbalancer"));
return mappings;
};
}
}

View File

@@ -29,12 +29,14 @@ import org.springframework.http.HttpMethod;
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
*/
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerProperties {
/**

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.client.loadbalancer;
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<ServiceInstance> loadBalancerFactory;
@Deprecated
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties,
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> 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<ServiceInstance> loadBalancerFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.lbRetryFactory = lbRetryFactory;
this.loadBalancerFactory = loadBalancerFactory;
@@ -159,14 +167,17 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
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<String, String> hint = loadBalancerFactory.getProperties(serviceId).getHint();
String defaultHint = hint.getOrDefault("default", "default");
String hintPropertyValue = hint.get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}

View File

@@ -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<String, LoadBalancerRetryPolicy> {
}
}

View File

@@ -22,6 +22,7 @@ import org.reactivestreams.Publisher;
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 @@ public interface ReactiveLoadBalancer<T> {
interface Factory<T> {
default LoadBalancerProperties getProperties(String serviceId) {
return null;
}
ReactiveLoadBalancer<T> getInstance(String serviceId);
/**

View File

@@ -24,9 +24,10 @@ 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.cloud.client.ServiceInstance;
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.web.reactive.function.client.WebClient;
@@ -42,6 +43,7 @@ import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
public class ReactorLoadBalancerClientAutoConfiguration {
@ConditionalOnMissingBean
@@ -49,9 +51,9 @@ public class ReactorLoadBalancerClientAutoConfiguration {
matchIfMissing = true)
@Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
ObjectProvider<List<LoadBalancerClientRequestTransformer>> transformers) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties,
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory,
transformers.getIfAvailable(Collections::emptyList));
}
@@ -59,18 +61,19 @@ public class ReactorLoadBalancerClientAutoConfiguration {
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
@Bean
public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
LoadBalancerRetryPolicy retryPolicy,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
LoadBalancerRetryPolicy.Factory retryPolicyFactory,
ObjectProvider<List<LoadBalancerClientRequestTransformer>> 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<ServiceInstance> loadBalancerFactory) {
return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.Factory(loadBalancerFactory);
}
}

View File

@@ -61,8 +61,6 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
private final LoadBalancerProperties properties;
private final List<LoadBalancerClientRequestTransformer> transformers;
/**
@@ -77,10 +75,16 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx
this(loadBalancerFactory, properties, Collections.emptyList());
}
@Deprecated
public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
LoadBalancerProperties properties, List<LoadBalancerClientRequestTransformer> transformers) {
this.loadBalancerFactory = loadBalancerFactory;
this.properties = properties;
this.transformers = transformers;
}
public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
List<LoadBalancerClientRequestTransformer> transformers) {
this.loadBalancerFactory = loadBalancerFactory;
this.transformers = transformers;
}
@@ -99,7 +103,7 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx
.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<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData, hint));
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
@@ -120,7 +124,8 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx
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);

View File

@@ -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 class RetryableExchangeFilterFunctionLoadBalancerRetryPolicy implements L
return HttpMethod.GET.equals(method) || properties.getRetry().isRetryOnAllOperations();
}
static class Factory implements LoadBalancerRetryPolicy.Factory {
final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
Factory(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
this.loadBalancerFactory = loadBalancerFactory;
}
@Override
public LoadBalancerRetryPolicy apply(String serviceId) {
return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(
loadBalancerFactory.getProperties(serviceId));
}
}
}

View File

@@ -69,9 +69,7 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
private static final List<Class<? extends Throwable>> 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<ServiceInstance> loadBalancerFactory;
@@ -87,22 +85,26 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
this(retryPolicy, loadBalancerFactory, properties, Collections.emptyList());
}
@Deprecated
public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retryPolicy,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
List<LoadBalancerClientRequestTransformer> transformers) {
this.retryPolicy = retryPolicy;
this.retryPolicyFactory = s -> retryPolicy;
this.loadBalancerFactory = loadBalancerFactory;
this.transformers = transformers;
}
public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy.Factory retryPolicyFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
List<LoadBalancerClientRequestTransformer> transformers) {
this.retryPolicyFactory = retryPolicyFactory;
this.loadBalancerFactory = loadBalancerFactory;
this.properties = properties;
this.transformers = transformers;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Mono<ClientResponse> 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 class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
}
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<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
@@ -154,7 +165,7 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
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 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
});
}).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 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
}).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 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
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 @@ public class RetryableLoadBalancerExchangeFilterFunction implements LoadBalanced
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);

View File

@@ -43,6 +43,7 @@ import org.springframework.core.env.PropertySource;
/**
* @author Ryan Baxter
*/
// TODO: 4.0.0 move to org.springframework.cloud.commons.config
public abstract class ConfigDataMissingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/**

View File

@@ -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<ConfigurationPropertyName, ConfigurationPropertyName> 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);
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.commons.config;
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 3.1.0
*/
public class DefaultsBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
private final Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings;
public DefaultsBindHandlerAdvisor(Map<ConfigurationPropertyName, ConfigurationPropertyName> additionalMappings) {
this.mappings = new LinkedHashMap<>();
if (!CollectionUtils.isEmpty(additionalMappings)) {
this.mappings.putAll(additionalMappings);
}
}
@Override
public BindHandler apply(BindHandler bindHandler) {
BindHandler handler = new AbstractBindHandler(bindHandler) {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
ConfigurationPropertyName defaultName = getDefaultName(name);
if (defaultName != null) {
BindResult<T> 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<ConfigurationPropertyName, ConfigurationPropertyName> 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<ConfigurationPropertyName, ConfigurationPropertyName> getDefaultMappings();
}
}

View File

@@ -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

View File

@@ -103,8 +103,8 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
}
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory() {
return new TestLoadBalancerFactory();
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory(LoadBalancerProperties properties) {
return new TestLoadBalancerFactory(properties);
}
}
@@ -176,6 +176,12 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
private static class TestLoadBalancerFactory implements ReactiveLoadBalancer.Factory<ServiceInstance> {
private final LoadBalancerProperties properties;
TestLoadBalancerFactory(LoadBalancerProperties properties) {
this.properties = properties;
}
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
throw new UnsupportedOperationException("Not implemented.");
@@ -191,6 +197,11 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
throw new UnsupportedOperationException("Not implemented.");
}
@Override
public LoadBalancerProperties getProperties(String serviceId) {
return properties;
}
}
}

View File

@@ -62,6 +62,7 @@ import static org.mockito.Mockito.mock;
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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
.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 class RetryLoadBalancerInterceptorTests {
.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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
.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 class RetryLoadBalancerInterceptorTests {
.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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
.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 class RetryLoadBalancerInterceptorTests {
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 class RetryLoadBalancerInterceptorTests {
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));

View File

@@ -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 @@ class LoadBalancerClientRequestTransformerTest {
void transformReactorLoadBalancerExchangeFilterFunction() {
ArgumentCaptor<ClientRequest> 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 @@ class LoadBalancerClientRequestTransformerTest {
void transformRetryableLoadBalancerExchangeFilterFunction() {
ArgumentCaptor<ClientRequest> 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();

View File

@@ -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.EnableAutoConfiguration;
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,16 +133,39 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
then(getFilters(builder)).isNullOrEmpty();
}
@Test
void defaultPropertiesWorks() {
ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.sources(OneWebClientBuilder.class, DefaulConfig.class)
.properties("spring.cloud.loadbalancer.health-check.initial-delay=1s",
"spring.cloud.loadbalancer.clients.myclient.health-check.interval=30s")
.run();
LoadBalancerClientsProperties properties = context.getBean(LoadBalancerClientsProperties.class);
then(properties.getClients()).containsKey("myclient");
LoadBalancerProperties clientProperties = properties.getClients().get("myclient");
// default value
then(clientProperties.getHealthCheck().getInitialDelay()).isEqualTo(Duration.ofSeconds(1));
// client specific value
then(clientProperties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30));
}
private ConfigurableApplicationContext init(Class<?> config) {
return LoadBalancerTestUtils.init(config, ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class);
}
@Configuration
@EnableAutoConfiguration
protected static class DefaulConfig {
}
@Configuration
protected static class NoWebClientBuilder {
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory() {
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<ServiceInstance>() {
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
@@ -153,6 +181,11 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
public <X> X getInstance(String name, Class<?> clazz, Class<?>... generics) {
throw new UnsupportedOperationException("Not implemented.");
}
@Override
public LoadBalancerProperties getProperties(String serviceId) {
return properties;
}
};
}
@@ -162,11 +195,6 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
};
}
@Bean
LoadBalancerProperties loadBalancerProperties() {
return new LoadBalancerProperties();
}
}
@Configuration

View File

@@ -171,7 +171,8 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
}
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient) {
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient,
LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<ServiceInstance>() {
private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle();
@@ -198,12 +199,12 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
public <X> X getInstance(String name, Class<?> clazz, Class<?>... generics) {
return null;
}
};
}
@Bean
LoadBalancerProperties loadBalancerProperties() {
return new LoadBalancerProperties();
@Override
public LoadBalancerProperties getProperties(String serviceId) {
return properties;
}
};
}
}

View File

@@ -228,7 +228,8 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests {
}
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient) {
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient,
LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<ServiceInstance>() {
private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle();
@@ -256,12 +257,12 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests {
public <X> X getInstance(String name, Class<?> clazz, Class<?>... generics) {
return null;
}
};
}
@Bean
LoadBalancerProperties loadBalancerProperties() {
return new LoadBalancerProperties();
@Override
public LoadBalancerProperties getProperties(String serviceId) {
return properties;
}
};
}
@Bean

View File

@@ -74,6 +74,7 @@ class RetryableLoadBalancerExchangeFilterFunctionTests {
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 @@ class RetryableLoadBalancerExchangeFilterFunctionTests {
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));

View File

@@ -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 @@ import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalanc
*/
public class BlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory {
private final LoadBalancerProperties loadBalancerProperties;
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties) {
this.loadBalancerProperties = loadBalancerProperties;
public BlockingLoadBalancedRetryFactory(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
this.loadBalancerFactory = loadBalancerFactory;
}
@Override
public LoadBalancedRetryPolicy createRetryPolicy(String serviceId, ServiceInstanceChooser serviceInstanceChooser) {
return new BlockingLoadBalancedRetryPolicy(loadBalancerProperties);
return new BlockingLoadBalancedRetryPolicy(loadBalancerFactory.getProperties(serviceId));
}
}

View File

@@ -23,10 +23,13 @@ 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.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.LoadBalancerClientsProperties;
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;
@@ -70,13 +73,14 @@ public class BlockingLoadBalancerClientAutoConfiguration {
@Configuration
@ConditionalOnClass(RetryTemplate.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
protected static class BlockingLoadBalancerRetryConfig {
@Bean
@ConditionalOnMissingBean
LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) {
return new BlockingLoadBalancedRetryFactory(properties);
LoadBalancedRetryFactory loadBalancedRetryFactory(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
return new BlockingLoadBalancedRetryFactory(loadBalancerFactory);
}
}

View File

@@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore;
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.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
@@ -40,7 +40,7 @@ import org.springframework.core.env.Environment;
*/
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@EnableConfigurationProperties(LoadBalancerProperties.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", havingValue = "true", matchIfMissing = true)
@@ -60,8 +60,8 @@ public class LoadBalancerAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}

View File

@@ -16,7 +16,12 @@
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;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration;
@@ -36,6 +41,8 @@ import org.springframework.core.env.Environment;
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
implements ReactiveLoadBalancer.Factory<ServiceInstance> {
private static final Log log = LogFactory.getLog(LoadBalancerClientFactory.class);
/**
* Property source name for load balancer.
*/
@@ -46,8 +53,16 @@ public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerC
*/
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
private final LoadBalancerClientsProperties properties;
@Deprecated
public LoadBalancerClientFactory() {
this(null);
}
public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
this.properties = properties;
}
public String getName(Environment environment) {
@@ -59,4 +74,21 @@ public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerC
return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}
@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;
}
// because specifics are overlayed on top of defaults, everything in `properties`,
// unless overridden, is in `clientsProperties`
return properties.getClients().get(serviceId);
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.boot.test.context.SpringBootTest;
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,8 @@ class CachingServiceInstanceListSupplierTests {
}
@Bean
LoadBalancerClientFactory loadBalancerClientFactory() {
return new LoadBalancerClientFactory();
LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
return new LoadBalancerClientFactory(properties);
}
@Bean
@@ -111,8 +112,8 @@ class CachingServiceInstanceListSupplierTests {
}
@Bean
public LoadBalancerProperties loadBalancerProperties() {
return new LoadBalancerProperties();
public LoadBalancerClientsProperties loadBalancerClientsProperties() {
return new LoadBalancerClientsProperties();
}
@Bean