Remove ribbon integration (#691)

* Remove Ribbon-specific configuration.

* Remove Ribbon-specific docs. Refactor.
This commit is contained in:
Olga Maciaszek-Sharma
2020-02-22 14:04:55 +01:00
committed by GitHub
parent 8a899f0bb0
commit 2710c8251d
18 changed files with 31 additions and 766 deletions

View File

@@ -34,7 +34,6 @@
|spring.cloud.loadbalancer.health-check.interval | 30s | Interval for rerunning the HealthCheck scheduler.
|spring.cloud.loadbalancer.health-check.path | |
|spring.cloud.loadbalancer.retry.enabled | true |
|spring.cloud.loadbalancer.ribbon.enabled | true | Causes `RibbonLoadBalancerClient` to be used by default.
|spring.cloud.refresh.enabled | true | Enables autoconfiguration for the refresh scope and associated features.
|spring.cloud.refresh.extra-refreshable | true | Additional class names for beans to post process into refresh scope.
|spring.cloud.service-registry.auto-registration.enabled | true | Whether service auto-registration is enabled. Defaults to true.

View File

@@ -389,18 +389,10 @@ CAUTION: A `RestTemplate` bean is no longer created through auto-configuration.
Individual applications must create it.
The URI needs to use a virtual host name (that is, a service name, not a host name).
The Ribbon client is used to create a full physical address.
See {githubroot}/spring-cloud-netflix/blob/master/spring-cloud-netflix-ribbon/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java[`RibbonAutoConfiguration`] for the details of how the `RestTemplate` is set up.
The BlockingLoadBalancerClient is used to create a full physical address.
IMPORTANT: To use a load-balanced `RestTemplate`, you need to have a load-balancer implementation in your classpath.
The recommended implementation is `BlockingLoadBalancerClient`.
Add <<spring-cloud-loadbalancer-starter, Spring Cloud LoadBalancer starter>> to your project in order to use it.
The
`RibbonLoadBalancerClient` also can be used, but it's now under maintenance and we do not recommend adding it to new projects.
WARNING: By default, if you have both `RibbonLoadBalancerClient` and `BlockingLoadBalancerClient`, to
preserve backward compatibility, `RibbonLoadBalancerClient` is used.
To override it, you can set the `spring.cloud.loadbalancer.ribbon.enabled` property to `false`.
[[webclinet-loadbalancer-client]]
=== Spring WebClient as a Load Balancer Client
@@ -434,29 +426,19 @@ public class MyClass {
====
The URI needs to use a virtual host name (that is, a service name, not a host name).
The Ribbon client or Spring Cloud LoadBalancer is used to create a full physical address.
The Spring Cloud LoadBalancer is used to create a full physical address.
IMPORTANT: If you want to use a `@LoadBalanced WebClient.Builder`, you need to have a load balancer
implementation in the classpath. We recommend that you add the
<<spring-cloud-loadbalancer-starter, Spring Cloud LoadBalancer starter>> to your project.
Then, `ReactiveLoadBalancer` is used underneath.
Alternatively, this functionality also works with `spring-cloud-starter-netflix-ribbon`, but the request
is handled by a non-reactive `LoadBalancerClient` under the hood. Additionally,
`spring-cloud-starter-netflix-ribbon` is already in maintenance mode, so we do not recommend
adding it to new projects.
If you have both `spring-cloud-starter-loadbalancer` and `spring-cloud-starter-netflix-ribbon`
in your classpath, Ribbon is used by default. To switch to Spring Cloud LoadBalancer,
set the `spring.cloud.loadbalancer.ribbon.enabled` property to `false`.
==== Retrying Failed Requests
A load-balanced `RestTemplate` can be configured to retry failed requests.
By default, this logic is disabled.
You can enable it by adding link:https://github.com/spring-projects/spring-retry[Spring Retry] to your application's classpath.
The load-balanced `RestTemplate` honors some of the Ribbon configuration values related to retrying failed requests.
You can use `client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and `client.ribbon.OkToRetryOnAllOperations` properties.
If you would like to disable the retry logic with Spring Retry on the classpath, you can set `spring.cloud.loadbalancer.retry.enabled=false`.
See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation] for a description of what these properties do.
If you would like to implement a `BackOffPolicy` in your retries, you need to create a bean of type `LoadBalancedRetryFactory` and override the `createBackOffPolicy` method:
@@ -478,8 +460,6 @@ public class MyConfiguration {
----
====
NOTE: `client` in the preceding examples should be replaced with your Ribbon client's name.
If you want to add one or more `RetryListener` implementations to your retry functionality, you need to
create a bean of type `LoadBalancedRetryListenerFactory` and return the `RetryListener` array
you would like to use for a given service, as the following example shows:
@@ -649,16 +629,9 @@ public class MyClass {
The URI needs to use a virtual host name (that is, a service name, not a host name).
The `ReactorLoadBalancer` is used to create a full physical address.
NOTE: By default, if you have `spring-cloud-netflix-ribbon` in your classpath, <<load-balancer-exchange-filter-function,`LoadBalancerExchangeFilterFunction`>>
is used to maintain backward compatibility. To use
`ReactorLoadBalancerExchangeFilterFunction`, set the `spring.cloud.loadbalancer.ribbon.enabled` property
to `false`.
[[load-balancer-exchange-filter-function]]
==== Spring WebFlux `WebClient` with a Non-reactive Load Balancer Client
If you you do not have <<spring-cloud-loadbalancer-starter, Spring Cloud LoadBalancer starter>> in your project
but you do have spring-cloud-starter-netflix-ribbon, you can still use `WebClient` with `LoadBalancerClient`.
If `spring-webflux` is on the classpath, `LoadBalancerExchangeFilterFunction`
is auto-configured. Note, however, that this
uses a non-reactive client under the hood.
@@ -1004,9 +977,6 @@ NOTE: Spring Cloud LoadBalancer starter includes
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html[Spring Boot Caching]
and https://github.com/stoyanr[Evictor].
WARNING: If you have both Ribbon and Spring Cloud LoadBalancer int the classpath, in order to maintain backward compatibility, Ribbon-based implementations will be used by default.
In order to switch to using Spring Cloud LoadBalancer under the hood, make sure you set the property `spring.cloud.loadbalancer.ribbon.enabled` to `false`.
[[custom-loadbalancer-configuration]]
=== Passing Your Own Spring Cloud LoadBalancer Configuration

View File

@@ -30,7 +30,7 @@ import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
import org.springframework.web.client.AsyncRestTemplate;
/**
* Auto-configuration for Ribbon (client-side load balancing).
* Auto-configuration for blocking client-side load balancing.
*
* @author Rob Worsnop
*/
@@ -49,13 +49,10 @@ public class AsyncLoadBalancerAutoConfiguration {
@Bean
public SmartInitializingSingleton loadBalancedAsyncRestTemplateInitializer(
final List<AsyncRestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (AsyncRestTemplate restTemplate : AsyncRestTemplateCustomizerConfig.this.restTemplates) {
for (AsyncRestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
return () -> {
for (AsyncRestTemplate restTemplate : AsyncRestTemplateCustomizerConfig.this.restTemplates) {
for (AsyncRestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
};
@@ -75,14 +72,11 @@ public class AsyncLoadBalancerAutoConfiguration {
@Bean
public AsyncRestTemplateCustomizer asyncRestTemplateCustomizer(
final AsyncLoadBalancerInterceptor loadBalancerInterceptor) {
return new AsyncRestTemplateCustomizer() {
@Override
public void customize(AsyncRestTemplate restTemplate) {
List<AsyncClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
return restTemplate -> {
List<AsyncClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}

View File

@@ -35,7 +35,7 @@ import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;
/**
* Auto-configuration for Ribbon (client-side load balancing).
* Auto-configuration for blocking (client-side load balancing).
*
* @author Spencer Gibb
* @author Dave Syer
@@ -79,7 +79,7 @@ public class LoadBalancerAutoConfiguration {
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
@@ -124,7 +124,7 @@ public class LoadBalancerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
public RetryLoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,

View File

@@ -18,11 +18,9 @@ package org.springframework.cloud.client.loadbalancer.reactive;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.ApplicationContext;
@@ -57,7 +55,6 @@ public class LoadBalancerBeanPostProcessorAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(OnNoRibbonDefaultCondition.class)
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
protected static class ReactorDeferringLoadBalancerFilterConfig {
@@ -71,22 +68,6 @@ public class LoadBalancerBeanPostProcessorAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(LoadBalancerClient.class)
@AutoConfigureAfter(ReactorDeferringLoadBalancerFilterConfig.class)
@Deprecated
protected static class ReactiveLoadBalancerConfig {
@Bean
@ConditionalOnMissingBean
DeferringLoadBalancerExchangeFilterFunction<LoadBalancerExchangeFilterFunction> deferringLoadBalancerExchangeFilterFunction(
ObjectProvider<LoadBalancerExchangeFilterFunction> exchangeFilterFunctionProvider) {
return new DeferringLoadBalancerExchangeFilterFunction<>(
exchangeFilterFunctionProvider);
}
}
static final class OnAnyLoadBalancerImplementationPresentCondition
extends AnyNestedCondition {

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
/**
* @author Spencer Gibb
* @author Ryan Baxter
* @deprecated Use {@link ReactorLoadBalancerExchangeFilterFunction} instead.
*/
@Deprecated
public class LoadBalancerExchangeFilterFunction implements ExchangeFilterFunction {
private static Log logger = LogFactory
.getLog(LoadBalancerExchangeFilterFunction.class);
private final LoadBalancerClient loadBalancerClient;
public LoadBalancerExchangeFilterFunction(LoadBalancerClient loadBalancerClient) {
this.loadBalancerClient = loadBalancerClient;
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI originalUrl = request.url();
String serviceId = originalUrl.getHost();
if (serviceId == null) {
String msg = String.format(
"Request URI does not contain a valid hostname: %s",
originalUrl.toString());
logger.warn(msg);
return Mono.just(
ClientResponse.create(HttpStatus.BAD_REQUEST).body(msg).build());
}
// TODO: reactive lb client
ServiceInstance instance = this.loadBalancerClient.choose(serviceId);
if (instance == null) {
String msg = String.format(
"Load balancer does not contain an instance for the service %s",
serviceId);
logger.warn(msg);
return Mono.just(ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE)
.body(msg).build());
}
URI uri = this.loadBalancerClient.reconstructURI(instance, originalUrl);
ClientRequest newRequest = ClientRequest.method(request.method(), uri)
.headers(headers -> headers.addAll(request.headers()))
.cookies(cookies -> cookies.addAll(request.cookies()))
.attributes(attributes -> attributes.putAll(request.attributes()))
.body(request.body()).build();
return next.exchange(newRequest);
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import java.util.concurrent.locks.Condition;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* A {@link Condition} that verifies that Ribbon is either not present or disabled.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
*/
public class OnNoRibbonDefaultCondition extends AnyNestedCondition {
public OnNoRibbonDefaultCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "false")
static class RibbonNotEnabled {
}
@ConditionalOnMissingClass("org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
static class RibbonLoadBalancerNotPresent {
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @deprecated in favour of {@link ReactorLoadBalancerClientAutoConfiguration}
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@ConditionalOnBean(LoadBalancerClient.class)
@AutoConfigureAfter(ReactorLoadBalancerClientAutoConfiguration.class)
@ConditionalOnMissingBean(ReactorLoadBalancerExchangeFilterFunction.class)
@Deprecated
public class ReactiveLoadBalancerAutoConfiguration {
@Bean
public LoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
LoadBalancerClient client) {
return new LoadBalancerExchangeFilterFunction(client);
}
}

View File

@@ -16,18 +16,11 @@
package org.springframework.cloud.client.loadbalancer.reactive;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@@ -44,46 +37,11 @@ import org.springframework.web.reactive.function.client.WebClient;
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
public class ReactorLoadBalancerClientAutoConfiguration {
@ConditionalOnMissingBean
@Bean
@ConditionalOnClass(
name = "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
public ReactorLoadBalancerClientRibbonWarnLogger reactorLoadBalancerClientRibbonWarnLogger() {
return new ReactorLoadBalancerClientRibbonWarnLogger();
}
@Configuration(proxyBeanMethods = false)
@Conditional(OnNoRibbonDefaultCondition.class)
protected static class ReactorLoadBalancerExchangeFilterFunctionConfig {
@ConditionalOnMissingBean
@Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory loadBalancerFactory) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory);
}
}
private static class ReactorLoadBalancerClientRibbonWarnLogger {
private static final Log LOG = LogFactory
.getLog(ReactorLoadBalancerClientRibbonWarnLogger.class);
@PostConstruct
void logWarning() {
if (LOG.isWarnEnabled()) {
LOG.warn("You have RibbonLoadBalancerClient on your classpath. "
+ "LoadBalancerExchangeFilterFunction that uses it under the "
+ "hood will be used by default. Spring Cloud Ribbon is now in maintenance mode, "
+ "so we suggest switching to "
+ ReactorLoadBalancerExchangeFilterFunction.class.getSimpleName()
+ " instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or "
+ "remove spring-cloud-starter-netflix-ribbon from your project.");
}
}
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory loadBalancerFactory) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory);
}
}

View File

@@ -12,7 +12,6 @@ org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\

View File

@@ -70,7 +70,7 @@ public class RetryLoadBalancerInterceptorTest {
};
@Before
public void setUp() throws Exception {
public void setUp() {
this.client = mock(LoadBalancerClient.class);
this.lbProperties = new LoadBalancerRetryProperties();
this.lbRequestFactory = mock(LoadBalancerRequestFactory.class);

View File

@@ -1,149 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties.SimpleServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author Spencer Gibb
* @author Ryan Baxter
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class LoadBalancerExchangeFilterFunctionTests {
@Autowired
private LoadBalancerExchangeFilterFunction lbFunction;
@Autowired
private SimpleDiscoveryProperties properties;
@LocalServerPort
private int port;
@Before
public void before() {
SimpleServiceInstance instance = new SimpleServiceInstance();
instance.setServiceId("testservice");
instance.setUri(URI.create("http://localhost:" + this.port));
this.properties.getInstances().put("testservice", Arrays.asList(instance));
}
@Test
public void testFilterFunctionWorks() {
String value = WebClient.builder().baseUrl("http://testservice")
.filter(this.lbFunction).build().get().uri("/hello").retrieve()
.bodyToMono(String.class).block();
then(value).isEqualTo("Hello World");
}
@Test
public void testNoInstance() {
ClientResponse clientResponse = WebClient.builder().baseUrl("http://foobar")
.filter(this.lbFunction).build().get().exchange().block();
then(clientResponse.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
}
@Test
public void testNoHostName() {
ClientResponse clientResponse = WebClient.builder().baseUrl("http:///foobar")
.filter(this.lbFunction).build().get().exchange().block();
then(clientResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
@EnableDiscoveryClient
@EnableAutoConfiguration
@SpringBootConfiguration
@RestController
static class Config {
@RequestMapping("/hello")
public String hello() {
return "Hello World";
}
@Bean
LoadBalancerClient loadBalancerClient(DiscoveryClient discoveryClient) {
return new LoadBalancerClient() {
Random random = new Random();
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
throw new UnsupportedOperationException();
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return UriComponentsBuilder.fromUri(original).host(instance.getHost())
.port(instance.getPort()).build().toUri();
}
@Override
public ServiceInstance choose(String serviceId) {
List<ServiceInstance> instances = discoveryClient
.getInstances(serviceId);
if (instances.size() == 0) {
return null;
}
int instanceIdx = this.random.nextInt(instances.size());
return instances.get(instanceIdx);
}
};
}
}
}

View File

@@ -1,248 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Random;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.assertLoadBalanced;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.getFilters;
/**
* @author Spencer Gibb
* @author Tim Ysewyn
* @author Olga Maciaszek-Sharma
*/
public class ReactiveLoadBalancerAutoConfigurationTests {
@Test
public void webClientBuilderGetsLoadBalancerInterceptor() {
ConfigurableApplicationContext context = init(OneWebClientBuilder.class);
final Map<String, WebClient.Builder> webClientBuilders = context
.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).isNotNull().hasSize(1);
WebClient.Builder webClientBuilder = webClientBuilders.values().iterator().next();
then(webClientBuilder).isNotNull();
assertLoadBalanced(webClientBuilder, LoadBalancerExchangeFilterFunction.class);
final Map<String, OneWebClientBuilder.TestService> testServiceMap = context
.getBeansOfType(OneWebClientBuilder.TestService.class);
then(testServiceMap).isNotNull().hasSize(1);
OneWebClientBuilder.TestService testService = testServiceMap.values().stream()
.findFirst().get();
assertLoadBalanced(testService.webClient,
LoadBalancerExchangeFilterFunction.class);
}
@Test
public void multipleWebClientBuilders() {
ConfigurableApplicationContext context = init(TwoWebClientBuilders.class);
final Map<String, WebClient.Builder> webClientBuilders = context
.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(2);
TwoWebClientBuilders.Two two = context.getBean(TwoWebClientBuilders.Two.class);
then(two.loadBalanced).isNotNull();
assertLoadBalanced(two.loadBalanced, LoadBalancerExchangeFilterFunction.class);
then(two.nonLoadBalanced).isNotNull();
then(getFilters(two.nonLoadBalanced)).isNullOrEmpty();
}
@Test
public void noCustomWebClientBuilders() {
ConfigurableApplicationContext context = init(NoWebClientBuilder.class);
final Map<String, WebClient.Builder> webClientBuilders = context
.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(1);
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
then(builder).isNotNull();
then(getFilters(builder)).isNullOrEmpty();
}
@Test
public void autoConfigurationNotLoadedWhenReactorLoadBalancerExchangeFilterFunctionPresent() {
ConfigurableApplicationContext context = init(
ReactorLoadBalancerClientPresent.class);
final Map<String, WebClient.Builder> webClientBuilders = context
.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(1);
assertThatThrownBy(
() -> context.getBean(LoadBalancerExchangeFilterFunction.class))
.isInstanceOf(NoSuchBeanDefinitionException.class);
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
then(builder).isNotNull();
assertLoadBalanced(builder, ReactorLoadBalancerExchangeFilterFunction.class);
}
private ConfigurableApplicationContext init(Class<?> config) {
return LoadBalancerTestUtils.init(config,
ReactiveLoadBalancerAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class);
}
@Configuration
protected static class NoWebClientBuilder {
@Bean
LoadBalancerClient loadBalancerClient() {
return new NoopLoadBalancerClient();
}
@Bean
LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
@Configuration
protected static class ReactorLoadBalancerClientPresent extends OneWebClientBuilder {
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory() {
return serviceId -> new TestReactiveLoadBalancer();
}
@Bean
ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction() {
return new ReactorLoadBalancerExchangeFilterFunction(
reactiveLoadBalancerFactory());
}
}
@Configuration
protected static class OneWebClientBuilder extends NoWebClientBuilder {
@Bean
@LoadBalanced
WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
@Bean
TestService testService() {
return new TestService(loadBalancedWebClientBuilder());
}
private final class TestService {
public final WebClient webClient;
private TestService(WebClient.Builder builder) {
this.webClient = builder.build();
}
}
}
@Configuration
protected static class TwoWebClientBuilders extends OneWebClientBuilder {
@Primary
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Configuration
protected static class Two {
@Autowired
WebClient.Builder nonLoadBalanced;
@Autowired
@LoadBalanced
WebClient.Builder loadBalanced;
}
}
private static class NoopLoadBalancerClient implements LoadBalancerClient {
private final Random random = new Random();
@Override
public ServiceInstance choose(String serviceId) {
return new DefaultServiceInstance(serviceId, serviceId, serviceId,
this.random.nextInt(40000), false);
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
try {
return request.apply(choose(serviceId));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
try {
return request.apply(choose(serviceId));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return DefaultServiceInstance.getUri(instance);
}
}
}

View File

@@ -16,25 +16,18 @@
package org.springframework.cloud.loadbalancer.config;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.reactive.OnNoRibbonDefaultCondition;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.client.RestTemplate;
/**
@@ -49,49 +42,15 @@ import org.springframework.web.client.RestTemplate;
@AutoConfigureBefore({
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@ConditionalOnClass(RestTemplate.class)
public class BlockingLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnClass(
name = "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
public BlockingLoadBalancerClientRibbonWarnLogger blockingLoadBalancerClientRibbonWarnLogger() {
return new BlockingLoadBalancerClientRibbonWarnLogger();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@Conditional(OnNoRibbonDefaultCondition.class)
protected static class BlockingLoadbalancerClientConfig {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@Primary
public BlockingLoadBalancerClient blockingLoadBalancerClient(
LoadBalancerClientFactory loadBalancerClientFactory) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
}
}
static class BlockingLoadBalancerClientRibbonWarnLogger {
private static final Log LOG = LogFactory
.getLog(BlockingLoadBalancerClientRibbonWarnLogger.class);
@PostConstruct
void logWarning() {
if (LOG.isWarnEnabled()) {
LOG.warn(
"You already have RibbonLoadBalancerClient on your classpath. It will be used by default. "
+ "As Spring Cloud Ribbon is in maintenance mode. We recommend switching to "
+ BlockingLoadBalancerClient.class.getSimpleName()
+ " instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or "
+ "remove spring-cloud-starter-netflix-ribbon from your project.");
}
}
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean
public LoadBalancerClient blockingLoadBalancerClient(
LoadBalancerClientFactory loadBalancerClientFactory) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
@@ -42,8 +41,7 @@ import org.springframework.core.env.Environment;
@LoadBalancerClients
@EnableConfigurationProperties(LoadBalancerProperties.class)
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class,
ReactiveLoadBalancerAutoConfiguration.class })
LoadBalancerBeanPostProcessorAutoConfiguration.class })
public class LoadBalancerAutoConfiguration {
private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

View File

@@ -32,13 +32,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cloud.client.loadbalancer.reactive.OnNoRibbonDefaultCondition;
import org.springframework.cloud.loadbalancer.cache.CaffeineBasedLoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.DefaultLoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
@@ -59,7 +57,6 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.cache.enabled",
matchIfMissing = true)
@EnableConfigurationProperties(LoadBalancerCacheProperties.class)
@Conditional(OnNoRibbonDefaultCondition.class)
public class LoadBalancerCacheAutoConfiguration {
@Configuration(proxyBeanMethods = false)

View File

@@ -1,10 +0,0 @@
{
"properties": [
{
"defaultValue": true,
"name": "spring.cloud.loadbalancer.ribbon.enabled",
"description": "Causes `RibbonLoadBalancerClient` to be used by default.",
"type": "java.lang.Boolean"
}
]
}

View File

@@ -22,7 +22,6 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration.BlockingLoadBalancerClientRibbonWarnLogger;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,17 +34,13 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BlockingLoadBalancerClientAutoConfigurationTests {
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.cloud.loadbalancer.ribbon.enabled=false")
.withConfiguration(AutoConfigurations.of(LoadBalancerAutoConfiguration.class,
BlockingLoadBalancerClientAutoConfiguration.class));
@Test
public void beansCreatedNormally() {
applicationContextRunner.run(ctxt -> {
assertThat(ctxt).hasSingleBean(BlockingLoadBalancerClient.class);
assertThat(ctxt)
.doesNotHaveBean(BlockingLoadBalancerClientRibbonWarnLogger.class);
});
applicationContextRunner.run(
ctxt -> assertThat(ctxt).hasSingleBean(BlockingLoadBalancerClient.class));
}
@Test
@@ -54,8 +49,6 @@ public class BlockingLoadBalancerClientAutoConfigurationTests {
.withClassLoader(new FilteredClassLoader(RestTemplate.class))
.run(context -> {
assertThat(context).doesNotHaveBean(BlockingLoadBalancerClient.class);
assertThat(context).doesNotHaveBean(
BlockingLoadBalancerClientRibbonWarnLogger.class);
});
}