diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 2691e5fe..f6652c1d 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -917,22 +917,14 @@ public class CustomLoadBalancerConfiguration { @Bean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( - ReactiveDiscoveryClient discoveryClient, Environment environment, - LoadBalancerZoneConfig zoneConfig, - ApplicationContext context) { - DiscoveryClientServiceInstanceListSupplier firstDelegate = new DiscoveryClientServiceInstanceListSupplier( - discoveryClient, environment); - ZonePreferenceServiceInstanceListSupplier delegate = new ZonePreferenceServiceInstanceListSupplier(firstDelegate, - zoneConfig); - ObjectProvider cacheManagerProvider = context - .getBeanProvider(LoadBalancerCacheManager.class); - if (cacheManagerProvider.getIfAvailable() != null) { - return new CachingServiceInstanceListSupplier(delegate, - cacheManagerProvider.getIfAvailable()); - } - return delegate; - } + ConfigurableApplicationContext context) { + return ServiceInstanceListSuppliers.builder() + .withDiscoveryClient() + .withZonePreference() + .withCaching() + .build(context); } +} ---- === Instance Health-Check for LoadBalancer @@ -966,25 +958,18 @@ public class CustomLoadBalancerConfiguration { @Bean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( - ReactiveDiscoveryClient discoveryClient, Environment environment, - LoadBalancerProperties loadBalancerProperties, - ApplicationContext context, - InstanceHealthChecker healthChecker) { - DiscoveryClientServiceInstanceListSupplier firstDelegate = new DiscoveryClientServiceInstanceListSupplier( - discoveryClient, environment); - HealthCheckServiceInstanceListSupplier delegate = new HealthCheckServiceInstanceListSupplier(firstDelegate, - loadBalancerProperties, healthChecker); - ObjectProvider cacheManagerProvider = context - .getBeanProvider(LoadBalancerCacheManager.class); - if (cacheManagerProvider.getIfAvailable() != null) { - return new CachingServiceInstanceListSupplier(delegate, - cacheManagerProvider.getIfAvailable()); - } - return delegate; - } + ConfigurableApplicationContext context) { + return ServiceInstanceListSuppliers.builder() + .withDiscoveryClient() + .withHealthChecks() + .withCaching() + .build(context); + } } ---- +TIP:: In order to make working on your own LoadBalancer configuration easier, we have added some utility methods in `ServiceInstanceListSuppliers` class. + [[spring-cloud-loadbalancer-starter]] === Spring Cloud LoadBalancer Starter diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java index c93d65b1..599ffd29 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java @@ -26,16 +26,16 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; -import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceSupplier; -import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceSupplier; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSuppliers; import org.springframework.cloud.loadbalancer.core.ServiceInstanceSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -71,17 +71,9 @@ public class LoadBalancerClientConfiguration { @ConditionalOnBean(ReactiveDiscoveryClient.class) @ConditionalOnMissingBean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( - ReactiveDiscoveryClient discoveryClient, Environment env, - ApplicationContext context) { - DiscoveryClientServiceInstanceListSupplier delegate = new DiscoveryClientServiceInstanceListSupplier( - discoveryClient, env); - ObjectProvider cacheManagerProvider = context - .getBeanProvider(LoadBalancerCacheManager.class); - if (cacheManagerProvider.getIfAvailable() != null) { - return new CachingServiceInstanceListSupplier(delegate, - cacheManagerProvider.getIfAvailable()); - } - return delegate; + ConfigurableApplicationContext context) { + return ServiceInstanceListSuppliers.builder().withDiscoveryClient() + .withCaching().build(context); } @Bean @@ -112,17 +104,9 @@ public class LoadBalancerClientConfiguration { @ConditionalOnBean(DiscoveryClient.class) @ConditionalOnMissingBean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( - DiscoveryClient discoveryClient, Environment env, - ApplicationContext context) { - DiscoveryClientServiceInstanceListSupplier delegate = new DiscoveryClientServiceInstanceListSupplier( - discoveryClient, env); - ObjectProvider cacheManagerProvider = context - .getBeanProvider(LoadBalancerCacheManager.class); - if (cacheManagerProvider.getIfAvailable() != null) { - return new CachingServiceInstanceListSupplier(delegate, - cacheManagerProvider.getIfAvailable()); - } - return delegate; + ConfigurableApplicationContext context) { + return ServiceInstanceListSuppliers.builder().withBlockingDiscoveryClient() + .withCaching().build(context); } @Bean diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java index ba9692c3..aecca695 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java @@ -37,7 +37,8 @@ import org.springframework.cloud.client.ServiceInstance; * @author Olga Maciaszek-Sharma * @since 2.2.0 */ -public class CachingServiceInstanceListSupplier implements ServiceInstanceListSupplier { +public class CachingServiceInstanceListSupplier + extends DelegatingServiceInstanceListSupplier { private static final Log log = LogFactory .getLog(CachingServiceInstanceListSupplier.class); @@ -48,14 +49,12 @@ public class CachingServiceInstanceListSupplier implements ServiceInstanceListSu public static final String SERVICE_INSTANCE_CACHE_NAME = CachingServiceInstanceListSupplier.class .getSimpleName() + "Cache"; - private final ServiceInstanceListSupplier delegate; - private final Flux> serviceInstances; @SuppressWarnings("unchecked") public CachingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, CacheManager cacheManager) { - this.delegate = delegate; + super(delegate); this.serviceInstances = CacheFlux.lookup(key -> { // TODO: configurable cache name Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME); @@ -70,7 +69,7 @@ public class CachingServiceInstanceListSupplier implements ServiceInstanceListSu return Mono.empty(); } return Flux.just(list).materialize().collectList(); - }, delegate.getServiceId()).onCacheMissResume(this.delegate) + }, delegate.getServiceId()).onCacheMissResume(delegate) .andWriteWith((key, signals) -> Flux.fromIterable(signals).dematerialize() .doOnNext(instances -> { Cache cache = cacheManager @@ -87,11 +86,6 @@ public class CachingServiceInstanceListSupplier implements ServiceInstanceListSu }).then()); } - @Override - public String getServiceId() { - return delegate.getServiceId(); - } - @Override public Flux> get() { return serviceInstances; diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java new file mode 100644 index 00000000..7cc9d53f --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java @@ -0,0 +1,46 @@ +/* + * 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.loadbalancer.core; + +import org.springframework.util.Assert; + +/** + * Represents a {@link ServiceInstanceListSupplier} that uses a delegate + * {@link ServiceInstanceListSupplier} instance underneath. + * + * @author Spencer Gibb + */ +public abstract class DelegatingServiceInstanceListSupplier + implements ServiceInstanceListSupplier { + + private final ServiceInstanceListSupplier delegate; + + public DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) { + Assert.notNull(delegate, "delegate may not be null"); + this.delegate = delegate; + } + + public ServiceInstanceListSupplier getDelegate() { + return this.delegate; + } + + @Override + public String getServiceId() { + return this.delegate.getServiceId(); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java index 1c5e3a9e..85ad174b 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java @@ -45,13 +45,12 @@ import org.springframework.web.util.UriComponentsBuilder; * @since 2.2.0 */ public class HealthCheckServiceInstanceListSupplier - implements ServiceInstanceListSupplier, InitializingBean, DisposableBean { + extends DelegatingServiceInstanceListSupplier + implements InitializingBean, DisposableBean { private static final Log LOG = LogFactory .getLog(HealthCheckServiceInstanceListSupplier.class); - private final ServiceInstanceListSupplier delegate; - private final LoadBalancerProperties.HealthCheck healthCheck; private final WebClient webClient; @@ -64,7 +63,7 @@ public class HealthCheckServiceInstanceListSupplier public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, LoadBalancerProperties.HealthCheck healthCheck, WebClient webClient) { - this.delegate = delegate; + super(delegate); this.healthCheck = healthCheck; defaultHealthCheckPath = healthCheck.getPath().getOrDefault("default", "/actuator/health"); @@ -121,11 +120,6 @@ public class HealthCheckServiceInstanceListSupplier }).repeatWhen(restart -> restart.delayElements(healthCheck.getInterval())); } - @Override - public String getServiceId() { - return delegate.getServiceId(); - } - @Override public Flux> get() { return aliveInstancesReplay; diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliers.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliers.java new file mode 100644 index 00000000..690a53ee --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliers.java @@ -0,0 +1,246 @@ +/* + * Copyright 2013-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.loadbalancer.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties; +import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; +import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * A utility class providing a {@link Builder} for creating a + * {@link ServiceInstanceListSupplier} hierarchy to be used in {@link ReactorLoadBalancer} + * configuration. + * + * @author Spencer Gibb + * @author Olga Maciaszek-Sharma + */ +public abstract class ServiceInstanceListSuppliers { + + private ServiceInstanceListSuppliers() { + + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Allows creating a {@link ServiceInstanceListSupplier} instance based on provided + * {@link ConfigurableApplicationContext}. + */ + public interface Creator extends + Function { + + } + + /** + * Allows creating a {@link ServiceInstanceListSupplier} instance based on provided + * {@link ConfigurableApplicationContext} and another + * {@link ServiceInstanceListSupplier} instance that will be used as a delegate. + */ + public interface DelegateCreator extends + BiFunction { + + } + + /** + * A builder for creating a {@link ServiceInstanceListSupplier} hierarchy to be used + * in {@link ReactorLoadBalancer} configuration. + */ + public static class Builder { + + private static final Log LOG = LogFactory.getLog(Builder.class); + + private Creator baseCreator; + + private DelegateCreator cachingCreator; + + private final List creators = new ArrayList<>(); + + public Builder() { + } + + /** + * Sets a blocking {@link DiscoveryClient}-based + * {@link DiscoveryClientServiceInstanceListSupplier} as a base + * {@link ServiceInstanceListSupplier} in the hierarchy. + * @return the {@link Builder} object + */ + public Builder withBlockingDiscoveryClient() { + if (baseCreator != null && LOG.isWarnEnabled()) { + LOG.warn( + "Overriding a previously set baseCreator with a blocking DiscoveryClient baseCreator."); + } + this.baseCreator = context -> { + DiscoveryClient discoveryClient = context.getBean(DiscoveryClient.class); + + return new DiscoveryClientServiceInstanceListSupplier(discoveryClient, + context.getEnvironment()); + }; + return this; + } + + /** + * Sets a {@link ReactiveDiscoveryClient}-based + * {@link DiscoveryClientServiceInstanceListSupplier} as a base + * {@link ServiceInstanceListSupplier} in the hierarchy. + * @return the {@link Builder} object + */ + public Builder withDiscoveryClient() { + if (baseCreator != null && LOG.isWarnEnabled()) { + LOG.warn( + "Overriding a previously set baseCreator with a ReactiveDiscoveryClient baseCreator."); + } + this.baseCreator = context -> { + ReactiveDiscoveryClient discoveryClient = context + .getBean(ReactiveDiscoveryClient.class); + + return new DiscoveryClientServiceInstanceListSupplier(discoveryClient, + context.getEnvironment()); + }; + return this; + } + + /** + * Sets a user-provided {@link ServiceInstanceListSupplier} as a base + * {@link ServiceInstanceListSupplier} in the hierarchy. + * @param supplier a user-provided {@link ServiceInstanceListSupplier} instance + * @return the {@link Builder} object + */ + public Builder withBase(ServiceInstanceListSupplier supplier) { + this.baseCreator = context -> supplier; + return this; + } + + /** + * Adds a {@link HealthCheckServiceInstanceListSupplier} to the + * {@link ServiceInstanceListSupplier} hierarchy. + * @return the {@link Builder} object + */ + public Builder withHealthChecks() { + DelegateCreator creator = (context, delegate) -> { + LoadBalancerProperties properties = context + .getBean(LoadBalancerProperties.class); + WebClient.Builder webClient = context.getBean(WebClient.Builder.class); + return new HealthCheckServiceInstanceListSupplier(delegate, + properties.getHealthCheck(), webClient.build()); + }; + this.creators.add(creator); + return this; + } + + /** + * Adds a {@link HealthCheckServiceInstanceListSupplier} that uses user-provided + * {@link WebClient} instance to the {@link ServiceInstanceListSupplier} + * hierarchy. + * @param webClient a user-provided {@link WebClient} instance + * @return the {@link Builder} object + */ + public Builder withHealthChecks(WebClient webClient) { + DelegateCreator creator = (context, delegate) -> { + LoadBalancerProperties properties = context + .getBean(LoadBalancerProperties.class); + return new HealthCheckServiceInstanceListSupplier(delegate, + properties.getHealthCheck(), webClient); + }; + this.creators.add(creator); + return this; + } + + /** + * Adds a {@link ZonePreferenceServiceInstanceListSupplier} to the + * {@link ServiceInstanceListSupplier} hierarchy. + * @return the {@link Builder} object + */ + public Builder withZonePreference() { + DelegateCreator creator = (context, delegate) -> { + LoadBalancerZoneConfig zoneConfig = context + .getBean(LoadBalancerZoneConfig.class); + return new ZonePreferenceServiceInstanceListSupplier(delegate, + zoneConfig); + }; + this.creators.add(creator); + return this; + } + + /** + * If {@link LoadBalancerCacheManager} is available in the context, wraps created + * {@link ServiceInstanceListSupplier} hierarchy with a + * {@link CachingServiceInstanceListSupplier} instance to provide a caching + * mechanism for service instances. Uses {@link ObjectProvider} to lazily resolve + * {@link LoadBalancerCacheManager}. + * @return the {@link Builder} object + */ + public Builder withCaching() { + if (cachingCreator != null && LOG.isWarnEnabled()) { + LOG.warn( + "Overriding a previously set cachingCreator with a CachingServiceInstanceListSupplier-based cachingCreator."); + } + this.cachingCreator = (context, delegate) -> { + ObjectProvider cacheManagerProvider = context + .getBeanProvider(LoadBalancerCacheManager.class); + if (cacheManagerProvider.getIfAvailable() != null) { + return new CachingServiceInstanceListSupplier(delegate, + cacheManagerProvider.getIfAvailable()); + } + if (LOG.isWarnEnabled()) { + LOG.warn( + "LoadBalancerCacheManager not available, returning delegate without caching."); + } + return delegate; + }; + return this; + } + + /** + * Builds the {@link ServiceInstanceListSupplier} hierarchy. + * @param context application context + * @return a {@link ServiceInstanceListSupplier} instance on top of the delegate + * hierarchy + */ + public ServiceInstanceListSupplier build(ConfigurableApplicationContext context) { + Assert.notNull(baseCreator, "A baseCreator must not be null"); + + ServiceInstanceListSupplier supplier = baseCreator.apply(context); + + for (DelegateCreator creator : creators) { + supplier = creator.apply(context, supplier); + } + + if (this.cachingCreator != null) { + supplier = this.cachingCreator.apply(context, supplier); + } + return supplier; + } + + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java index 54033471..76c6bbff 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java @@ -36,30 +36,23 @@ import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; * @since 2.2.1 */ public class ZonePreferenceServiceInstanceListSupplier - implements ServiceInstanceListSupplier { + extends DelegatingServiceInstanceListSupplier { private final String ZONE = "zone"; - private final ServiceInstanceListSupplier delegate; - private final LoadBalancerZoneConfig zoneConfig; private String zone; public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, LoadBalancerZoneConfig zoneConfig) { - this.delegate = delegate; + super(delegate); this.zoneConfig = zoneConfig; } - @Override - public String getServiceId() { - return delegate.getServiceId(); - } - @Override public Flux> get() { - return delegate.get().map(this::filteredByZone); + return getDelegate().get().map(this::filteredByZone); } private List filteredByZone(List serviceInstances) { diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTests.java new file mode 100644 index 00000000..39208e52 --- /dev/null +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013-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.loadbalancer.core; + +import org.junit.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties; +import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +public class ServiceInstanceListSuppliersTests { + + @Test + public void testBuilder() { + new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class) + .run(context -> { + ServiceInstanceListSupplier supplier = ServiceInstanceListSuppliers + .builder().withDiscoveryClient().withHealthChecks() + .withCaching().build(context); + assertThat(supplier) + .isInstanceOf(CachingServiceInstanceListSupplier.class); + DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; + assertThat(delegating.getDelegate()) + .isInstanceOf(HealthCheckServiceInstanceListSupplier.class); + delegating = (DelegatingServiceInstanceListSupplier) delegating + .getDelegate(); + assertThat(delegating.getDelegate()).isInstanceOf( + DiscoveryClientServiceInstanceListSupplier.class); + }); + } + + @Test + public void testIllegalArgumentExceptionThrownWhenBaseBuilderNull() { + new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class) + .run(context -> { + try { + ServiceInstanceListSuppliers.builder().withHealthChecks() + .build(context); + fail("Should have thrown exception."); + } + catch (Exception exception) { + assertThat(exception) + .isInstanceOf(IllegalArgumentException.class); + } + + }); + } + + @Test + public void testDelegateReturnedIfLoadBalancerCacheManagerNotAvailable() { + new ApplicationContextRunner().withUserConfiguration(BaseTestConfig.class) + .run(context -> { + ServiceInstanceListSupplier supplier = ServiceInstanceListSuppliers + .builder().withDiscoveryClient().withHealthChecks() + .withCaching().build(context); + assertThat(supplier) + .isNotInstanceOf(CachingServiceInstanceListSupplier.class); + assertThat(supplier) + .isInstanceOf(HealthCheckServiceInstanceListSupplier.class); + DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; + assertThat(delegating.getDelegate()).isInstanceOf( + DiscoveryClientServiceInstanceListSupplier.class); + }); + } + + @Import(BaseTestConfig.class) + private static class CacheTestConfig { + + @Bean + public LoadBalancerCacheManager cacheManager() { + return mock(LoadBalancerCacheManager.class); + } + + } + + private static class BaseTestConfig { + + @Bean + public ReactiveDiscoveryClient reactiveDiscoveryClient() { + return mock(ReactiveDiscoveryClient.class); + } + + @Bean + public LoadBalancerProperties loadBalancerProperties() { + return new LoadBalancerProperties(); + } + + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + } + +}