From da0ce3c4c8e10a8f442766826e089597408f6fbe Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Wed, 18 Sep 2019 17:56:52 +0200 Subject: [PATCH] Gh 595 add serviceinstancelistsupplier (#607) * Add ServiceInstanceListSupplier and its implementations. Deprecate ServiceInstanceSupplier and its implementations. Fixes gh-595. * Switch to non-parameterised type. * Switch autoConfiguration and tests to new implementation. * Update docs and javadocs. Remove deprecated TODOs. * Fix after code review. --- .../main/asciidoc/spring-cloud-commons.adoc | 2 +- .../LoadBalancerClientConfiguration.java | 23 ++++- .../CachingServiceInstanceListSupplier.java | 83 +++++++++++++++++++ .../core/CachingServiceInstanceSupplier.java | 8 +- ...veryClientServiceInstanceListSupplier.java | 59 +++++++++++++ ...iscoveryClientServiceInstanceSupplier.java | 2 + .../core/NoopServiceInstanceListSupplier.java | 42 ++++++++++ .../core/NoopServiceInstanceSupplier.java | 42 ++++++++++ .../core/RoundRobinLoadBalancer.java | 82 +++++++++++++++--- .../core/ServiceInstanceListSupplier.java | 37 +++++++++ .../core/ServiceInstanceSupplier.java | 2 + .../support/ServiceInstanceListSuppliers.java | 60 ++++++++++++++ .../support/ServiceInstanceSuppliers.java | 2 + .../BlockingLoadBalancerClientTests.java | 2 +- ...dBalancerClientAutoConfigurationTests.java | 2 +- ...lancerTest.java => LoadBalancerTests.java} | 20 ++++- 16 files changed, 442 insertions(+), 26 deletions(-) create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceListSupplier.java create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceSupplier.java create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplier.java create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceListSuppliers.java rename spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/{LoadBalancerTest.java => LoadBalancerTests.java} (87%) diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 32789d15..f737493e 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -418,7 +418,7 @@ will be handled by a non-reactive `LoadBalancerClient` under the hood. Additiona spring-cloud-starter-netflix-ribbon is already in maintenance mode, so we do not recommend adding it to new projects. -IMPORTANT: In order to make use of the more efficient cached version of `ServiceInstanceSupplier`, +IMPORTANT: In order to make use of the more efficient cached version of `ServiceInstanceListSupplier`, `spring-cloud-starter-loadbalancer` will *enable caching* by default. https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html[Spring Boot Caching] mechanism will be used under the hood. If you don't want caching to be used, you can set 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 fc7b38e2..aaaa3cb3 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 @@ -24,10 +24,13 @@ import org.springframework.cache.CacheManager; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +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.ServiceInstanceSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; @@ -43,13 +46,27 @@ import org.springframework.core.env.Environment; @ConditionalOnDiscoveryEnabled public class LoadBalancerClientConfiguration { + @Bean + @ConditionalOnBean(DiscoveryClient.class) + @ConditionalOnMissingBean + public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( + DiscoveryClient discoveryClient, Environment env, + ObjectProvider cacheManager) { + DiscoveryClientServiceInstanceListSupplier delegate = new DiscoveryClientServiceInstanceListSupplier( + discoveryClient, env); + if (cacheManager.getIfAvailable() != null) { + return new CachingServiceInstanceListSupplier(delegate, + cacheManager.getIfAvailable()); + } + return delegate; + } + @Bean @ConditionalOnBean(DiscoveryClient.class) @ConditionalOnMissingBean public ServiceInstanceSupplier discoveryClientServiceInstanceSupplier( DiscoveryClient discoveryClient, Environment env, ObjectProvider cacheManager) { - // TODO: bean post processor to enable caching? DiscoveryClientServiceInstanceSupplier delegate = new DiscoveryClientServiceInstanceSupplier( discoveryClient, env); if (cacheManager.getIfAvailable() != null) { @@ -65,8 +82,8 @@ public class LoadBalancerClientConfiguration { Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); - return new RoundRobinLoadBalancer(name, loadBalancerClientFactory - .getLazyProvider(name, ServiceInstanceSupplier.class)); + return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, + ServiceInstanceListSupplier.class), name); } } 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 new file mode 100644 index 00000000..d5a20bf1 --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplier.java @@ -0,0 +1,83 @@ +/* + * 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 java.util.List; +import java.util.Objects; + +import reactor.cache.CacheFlux; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cloud.client.ServiceInstance; + +/** + * A {@link ServiceInstanceListSupplier} implementation that tries retrieving + * {@link ServiceInstance} objects from cache; if none found, retrieves instances using + * {@link DiscoveryClientServiceInstanceListSupplier}. + * + * @author Spencer Gibb + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +public class CachingServiceInstanceListSupplier implements ServiceInstanceListSupplier { + + /** + * Name of the service cache instance. + */ + 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; + this.serviceInstances = CacheFlux.lookup(key -> { + // TODO: configurable cache name + Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME); + List list = Objects.requireNonNull(cache).get(key, + List.class); + if (list == null || list.isEmpty()) { + return Mono.empty(); + } + return Flux.just(list).materialize().collectList(); + }, delegate.getServiceId()).onCacheMissResume(this.delegate) + .andWriteWith((key, signals) -> Flux.fromIterable(signals).dematerialize() + .doOnNext(instances -> { + Cache cache = cacheManager + .getCache(SERVICE_INSTANCE_CACHE_NAME); + Objects.requireNonNull(cache).put(key, instances); + }).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/CachingServiceInstanceSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceSupplier.java index 6118cc91..90626d85 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceSupplier.java @@ -27,8 +27,10 @@ import org.springframework.cache.CacheManager; import org.springframework.cloud.client.ServiceInstance; /** + * @deprecated Use {@link CachingServiceInstanceListSupplier} instead. * @author Spencer Gibb */ +@Deprecated public class CachingServiceInstanceSupplier implements ServiceInstanceSupplier { /** @@ -46,10 +48,8 @@ public class CachingServiceInstanceSupplier implements ServiceInstanceSupplier { CacheManager cacheManager) { this.delegate = delegate; this.serviceInstances = CacheFlux.lookup(key -> { - Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME); // TODO: - // configurable - // cache - // name + // TODO: configurable cache name + Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME); List list = cache.get(key, List.class); if (list == null || list.isEmpty()) { return Mono.empty(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java new file mode 100644 index 00000000..2ba4161f --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.List; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.core.env.Environment; + +import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME; + +/** + * A discovery-client-based {@link ServiceInstanceListSupplier} implementation. + * + * @author Spencer Gibb + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +public class DiscoveryClientServiceInstanceListSupplier + implements ServiceInstanceListSupplier { + + private final DiscoveryClient delegate; + + private final String serviceId; + + public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, + Environment environment) { + this.delegate = delegate; + this.serviceId = environment.getProperty(PROPERTY_NAME); + } + + @Override + public String getServiceId() { + return serviceId; + } + + @Override + public Flux> get() { + return Flux.defer(() -> Flux.just(delegate.getInstances(serviceId))); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceSupplier.java index 616187be..b6a8b53a 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceSupplier.java @@ -27,8 +27,10 @@ import org.springframework.core.env.Environment; import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME; /** + * @deprecated Use {@link DiscoveryClientServiceInstanceListSupplier} instead. * @author Spencer Gibb */ +@Deprecated public class DiscoveryClientServiceInstanceSupplier implements ServiceInstanceSupplier { private final DiscoveryClient delegate; diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceListSupplier.java new file mode 100644 index 00000000..255a3c0c --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceListSupplier.java @@ -0,0 +1,42 @@ +/* + * 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 java.util.List; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * A no-op implementation of {@link ServiceInstanceListSupplier}. + * + * @author Olga Maciaszek-Sharma + */ +public class NoopServiceInstanceListSupplier implements ServiceInstanceListSupplier { + + @Override + public String getServiceId() { + return ""; + } + + @Override + public Flux> get() { + return Flux.empty(); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceSupplier.java new file mode 100644 index 00000000..fd4d0cb8 --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/NoopServiceInstanceSupplier.java @@ -0,0 +1,42 @@ +/* + * 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 reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * A no-op implementation of {@link ServiceInstanceSupplier}. + * + * @author Olga Maciaszek-Sharma + * @deprecated Use {@link NoopServiceInstanceListSupplier} instead. + */ +@Deprecated +public class NoopServiceInstanceSupplier implements ServiceInstanceSupplier { + + @Override + public String getServiceId() { + return ""; + } + + @Override + public Flux get() { + return Flux.empty(); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java index aebfef8b..9b74ff31 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java @@ -16,6 +16,7 @@ package org.springframework.cloud.loadbalancer.core; +import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -31,7 +32,10 @@ import org.springframework.cloud.client.loadbalancer.reactive.Request; import org.springframework.cloud.client.loadbalancer.reactive.Response; /** + * A Round-Robin-based implementation of {@link ReactorServiceInstanceLoadBalancer}. + * * @author Spencer Gibb + * @author Olga Maciaszek-Sharma */ public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer { @@ -39,15 +43,59 @@ public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalance private final AtomicInteger position; - private final ObjectProvider serviceInstanceSupplier; + @Deprecated + private ObjectProvider serviceInstanceSupplier; + + private ObjectProvider serviceInstanceListSupplierProvider; private final String serviceId; + /** + * @param serviceId id of the service for which to choose an instance + * @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that + * will be used to get available instances + * @deprecated Use {@link #RoundRobinLoadBalancer(ObjectProvider, String)}} instead. + */ + @Deprecated public RoundRobinLoadBalancer(String serviceId, ObjectProvider serviceInstanceSupplier) { this(serviceId, serviceInstanceSupplier, new Random().nextInt(1000)); } + /** + * @param serviceInstanceListSupplierProvider a provider of + * {@link ServiceInstanceListSupplier} that will be used to get available instances + * @param serviceId id of the service for which to choose an instance + */ + public RoundRobinLoadBalancer( + ObjectProvider serviceInstanceListSupplierProvider, + String serviceId) { + this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000)); + } + + /** + * @param serviceInstanceListSupplierProvider a provider of + * {@link ServiceInstanceListSupplier} that will be used to get available instances + * @param serviceId id of the service for which to choose an instance + * @param seedPosition Round Robin element position marker + */ + public RoundRobinLoadBalancer( + ObjectProvider serviceInstanceListSupplierProvider, + String serviceId, int seedPosition) { + this.serviceId = serviceId; + this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; + this.position = new AtomicInteger(seedPosition); + } + + /** + * @param serviceId id of the service for which to choose an instance + * @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that + * will be used to get available instances + * @param seedPosition Round Robin element position marker + * @deprecated Use {@link #RoundRobinLoadBalancer(ObjectProvider, String, int)}} + * instead. + */ + @Deprecated public RoundRobinLoadBalancer(String serviceId, ObjectProvider serviceInstanceSupplier, int seedPosition) { @@ -62,19 +110,29 @@ public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalance // src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java public Mono> choose(Request request) { // TODO: move supplier to Request? - ServiceInstanceSupplier supplier = this.serviceInstanceSupplier.getIfAvailable(); - return supplier.get().collectList().map(instances -> { - if (instances.isEmpty()) { - log.warn("No servers available for service: " + this.serviceId); - return new EmptyResponse(); - } - // TODO: enforce order? - int pos = Math.abs(this.position.incrementAndGet()); + // Temporary conditional logic till deprecated members are removed. + if (serviceInstanceListSupplierProvider != null) { + ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider + .getIfAvailable(NoopServiceInstanceListSupplier::new); + return supplier.get().next().map(this::getInstanceResponse); + } + ServiceInstanceSupplier supplier = this.serviceInstanceSupplier + .getIfAvailable(NoopServiceInstanceSupplier::new); + return supplier.get().collectList().map(this::getInstanceResponse); + } - ServiceInstance instance = instances.get(pos % instances.size()); + private Response getInstanceResponse( + List instances) { + if (instances.isEmpty()) { + log.warn("No servers available for service: " + this.serviceId); + return new EmptyResponse(); + } + // TODO: enforce order? + int pos = Math.abs(this.position.incrementAndGet()); - return new DefaultResponse(instance); - }); + ServiceInstance instance = instances.get(pos % instances.size()); + + return new DefaultResponse(instance); } } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplier.java new file mode 100644 index 00000000..2e79956d --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplier.java @@ -0,0 +1,37 @@ +/* + * 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 java.util.List; +import java.util.function.Supplier; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * A {@link Supplier} of lists of {@link ServiceInstance} objects. + * + * @author Olga Maciaszek-Sharma + * @since 2.2.0 + */ +public interface ServiceInstanceListSupplier + extends Supplier>> { + + String getServiceId(); + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceSupplier.java index 56d6cc10..422f6471 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceSupplier.java @@ -24,7 +24,9 @@ import org.springframework.cloud.client.ServiceInstance; /** * @author Spencer Gibb + * @deprecated Use {@link ServiceInstanceListSupplier} instead. */ +@Deprecated public interface ServiceInstanceSupplier extends Supplier> { String getServiceId(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceListSuppliers.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceListSuppliers.java new file mode 100644 index 00000000..1d275adb --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceListSuppliers.java @@ -0,0 +1,60 @@ +/* + * 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.support; + +import java.util.Arrays; +import java.util.List; + +import reactor.core.publisher.Flux; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; + +/** + * Utility class for service instances. + * + * @author Spencer Gibb + * @author Olga Maciaszek-Sharma + */ +public final class ServiceInstanceListSuppliers { + + private ServiceInstanceListSuppliers() { + throw new IllegalStateException("Can't instantiate a utility class"); + } + + public static ServiceInstanceListSupplier from(String serviceId, + ServiceInstance... instances) { + return new ServiceInstanceListSupplier() { + @Override + public Flux> get() { + return Flux.just(Arrays.asList(instances)); + } + + @Override + public String getServiceId() { + return serviceId; + } + }; + } + + public static ObjectProvider toProvider(String serviceId, + ServiceInstance... instances) { + return new SimpleObjectProvider<>(from(serviceId, instances)); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceSuppliers.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceSuppliers.java index 1a435f1a..c245a596 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceSuppliers.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/support/ServiceInstanceSuppliers.java @@ -26,7 +26,9 @@ import org.springframework.cloud.loadbalancer.core.ServiceInstanceSupplier; * Utility class for service instances. * * @author Spencer Gibb + * @deprecated Use {@link ServiceInstanceListSuppliers} instead. */ +@Deprecated public final class ServiceInstanceSuppliers { private ServiceInstanceSuppliers() { diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java index 51b66cf0..025105d0 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java @@ -96,7 +96,7 @@ class BlockingLoadBalancerClientTests { } @Test - void exceptionThrownIfInstanceNotAvailableForRequestExecution() throws IOException { + void exceptionThrownIfInstanceNotAvailableForRequestExecution() { try { final String result = "result"; Object actualResult = loadBalancerClient.execute("unknownservice", diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java index 70aaa95d..cc945371 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class BlockingLoadBalancerClientAutoConfigurationTests { - ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner() + private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.cloud.loadbalancer.ribbon.enabled=false", "debug=true") .withConfiguration(AutoConfigurations.of(LoadBalancerAutoConfiguration.class, diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTests.java similarity index 87% rename from spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTest.java rename to spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTests.java index 6ae9138c..a60b498f 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerTests.java @@ -38,6 +38,7 @@ import org.springframework.cloud.client.loadbalancer.reactive.Response; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; import org.springframework.cloud.loadbalancer.support.ServiceInstanceSuppliers; import org.springframework.context.annotation.Bean; import org.springframework.core.ResolvableType; @@ -51,7 +52,7 @@ import static org.assertj.core.api.BDDAssertions.then; */ @RunWith(SpringRunner.class) @SpringBootTest -public class LoadBalancerTest { +public class LoadBalancerTests { @Autowired private LoadBalancerClientFactory clientFactory; @@ -124,9 +125,20 @@ public class LoadBalancerTest { assertLoadBalancer(loadBalancer, Arrays.asList("1host", "2host-secure")); } + @Test + public void staticConfigurationWorksWithServiceInstanceListSupplier() { + String serviceId = "test1"; + RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer( + ServiceInstanceListSuppliers.toProvider(serviceId, + instance(serviceId, "1host", false), + instance(serviceId, "2host-secure", true)), + serviceId, -1); + assertLoadBalancer(loadBalancer, Arrays.asList("1host", "2host-secure")); + } + private DefaultServiceInstance instance(String serviceId, String host, boolean secure) { - return new DefaultServiceInstance(serviceId, host, 80, secure); + return new DefaultServiceInstance(serviceId, serviceId, host, 80, secure); } @EnableAutoConfiguration @@ -147,8 +159,8 @@ public class LoadBalancerTest { public RoundRobinLoadBalancer roundRobinContextLoadBalancer( LoadBalancerClientFactory clientFactory, Environment env) { String serviceId = clientFactory.getName(env); - return new RoundRobinLoadBalancer(serviceId, clientFactory - .getLazyProvider(serviceId, ServiceInstanceSupplier.class), -1); + return new RoundRobinLoadBalancer(clientFactory.getLazyProvider(serviceId, + ServiceInstanceListSupplier.class), serviceId, -1); } }