@LoadBalanced RestClient (#1294)
This commit is contained in:
committed by
GitHub
parent
42ba0eef31
commit
dd47ca6c29
@@ -135,7 +135,7 @@ Please see the documentation of the `ServiceRegistry` implementation you use for
|
||||
For instance, Eureka's supported statuses are `UP`, `DOWN`, `OUT_OF_SERVICE`, and `UNKNOWN`.
|
||||
|
||||
[[rest-template-loadbalancer-client]]
|
||||
== Spring RestTemplate as a Load Balancer Client
|
||||
== Spring `RestTemplate` as a LoadBalancer Client
|
||||
|
||||
You can configure a `RestTemplate` to use a Load-balancer client.
|
||||
To create a load-balanced `RestTemplate`, create a `RestTemplate` `@Bean` and use the `@LoadBalanced` qualifier, as the following example shows:
|
||||
@@ -157,8 +157,8 @@ public class MyClass {
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public String doOtherStuff() {
|
||||
String results = restTemplate.getForObject("http://stores/stores", String.class);
|
||||
return results;
|
||||
String result = restTemplate.getForObject("http://stores/stores", String.class);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -167,13 +167,140 @@ 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 BlockingLoadBalancerClient is used to create a full physical address.
|
||||
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.
|
||||
IMPORTANT: To use a load-balanced `RestTemplate`, you need to have a Spring Cloud LoadBalancer implementation in your classpath.
|
||||
Add xref:spring-cloud-commons/loadbalancer.adoc#spring-cloud-loadbalancer-starter[Spring Cloud LoadBalancer starter] to your project in order to use it.
|
||||
|
||||
[[multiple-resttemplate-objects]]
|
||||
=== Multiple `RestTemplate` Objects
|
||||
|
||||
If you want a `RestTemplate` that is not load-balanced, create a `RestTemplate` bean and inject it.
|
||||
To access the load-balanced `RestTemplate`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestTemplate loadBalanced() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
private RestTemplate loadBalanced;
|
||||
|
||||
public String doOtherStuff() {
|
||||
return loadBalanced.getForObject("http://stores/stores", String.class);
|
||||
}
|
||||
|
||||
public String doStuff() {
|
||||
return restTemplate.getForObject("http://example.com", String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplate` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
|
||||
|
||||
TIP: If you see errors such as `java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89`, try injecting `RestOperations` or setting `spring.aop.proxyTargetClass=true`.
|
||||
|
||||
[[rest-client-loadbalancer-client]]
|
||||
== Spring `RestClient` as a LoadBalancer Client
|
||||
|
||||
You can configure a `RestClient` to use a Load-balancer client.
|
||||
To create a load-balanced `RestClient`, create a `RestClient.Builder` `@Bean` and use the `@LoadBalanced` qualifier, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestClient.Builder restClientBuilder() {
|
||||
return RestClient.builder();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
|
||||
@Autowired
|
||||
private RestClient.Builder restClientBuilder;
|
||||
|
||||
public String doOtherStuff() {
|
||||
return restClientBuilder.build().get().uri(URI.create("http://stores/stores")).retrieve().body(String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The URI needs to use a virtual host name (that is, a service name, not a host name).
|
||||
The `BlockingLoadBalancerClient` is used to create a full physical address.
|
||||
|
||||
IMPORTANT: To use a load-balanced `RestClient`, you need to have a Spring LoadBalancer implementation in your classpath.
|
||||
Add xref:spring-cloud-commons/loadbalancer.adoc#spring-cloud-loadbalancer-starter[Spring Cloud LoadBalancer starter] to your project in order to use it.
|
||||
|
||||
[[multiple-restclient-objects]]
|
||||
=== Multiple `RestClient.Builder` Objects
|
||||
|
||||
If you want a `RestClient.Builder` that is not load-balanced, create a `RestClient.Builder` bean and inject it.
|
||||
To access the load-balanced `RestClient`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestClient.Builder loadBalanced() {
|
||||
return RestClient.builder();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
RestClient.Builder restClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
@Autowired
|
||||
private RestClient.Builder restClientBuilder;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
private RestClient.Builder loadBalanced;
|
||||
|
||||
public String doOtherStuff() {
|
||||
return restClientBuilder.build().get().uri("http://stores/stores")
|
||||
.retrieve().body(String.class);
|
||||
}
|
||||
|
||||
public String doStuff() {
|
||||
return restClientBuilder.build().get().uri("http://example.com")
|
||||
.retrieve().body(String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplate` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
|
||||
|
||||
[[webclinet-loadbalancer-client]]
|
||||
== Spring WebClient as a Load Balancer Client
|
||||
== Spring `WebClient` as a LoadBalancer Client
|
||||
|
||||
You can configure `WebClient` to automatically use a load-balancer client.
|
||||
To create a load-balanced `WebClient`, create a `WebClient.Builder` `@Bean` and use the `@LoadBalanced` qualifier, as follows:
|
||||
@@ -204,11 +331,55 @@ public class MyClass {
|
||||
The URI needs to use a virtual host name (that is, a service name, not a host name).
|
||||
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
|
||||
IMPORTANT: If you want to use a `@LoadBalanced WebClient.Builder`, you need to have a Spring Cloud LoadBalancer
|
||||
implementation in the classpath. We recommend that you add the
|
||||
xref:spring-cloud-commons/loadbalancer.adoc#spring-cloud-loadbalancer-starter[Spring Cloud LoadBalancer starter] to your project.
|
||||
Then, `ReactiveLoadBalancer` is used underneath.
|
||||
|
||||
[[multiple-webclient-objects]]
|
||||
=== Multiple `WebClient.Builder` Objects
|
||||
|
||||
If you want a `WebClient.Buider` that is not load-balanced, create a `WebClient` bean and inject it.
|
||||
To access the load-balanced `WebClient.Builder`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
WebClient.Builder loadBalanced() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
WebClient.Builder webClientBuilder() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
@Autowired
|
||||
private WebClient.Builder webClientBuilder;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
private WebClient.Builder loadBalanced;
|
||||
|
||||
public Mono<String> doOtherStuff() {
|
||||
return loadBalanced.build().get().uri("http://stores/stores")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
|
||||
public Mono<String> doStuff() {
|
||||
return webClientBuilder.build().get().uri("http://example.com")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[retrying-failed-requests]]
|
||||
=== Retrying Failed Requests
|
||||
|
||||
@@ -295,96 +466,6 @@ public class MyConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
[[multiple-resttemplate-objects]]
|
||||
== Multiple `RestTemplate` Objects
|
||||
|
||||
If you want a `RestTemplate` that is not load-balanced, create a `RestTemplate` bean and inject it.
|
||||
To access the load-balanced `RestTemplate`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestTemplate loadBalanced() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
private RestTemplate loadBalanced;
|
||||
|
||||
public String doOtherStuff() {
|
||||
return loadBalanced.getForObject("http://stores/stores", String.class);
|
||||
}
|
||||
|
||||
public String doStuff() {
|
||||
return restTemplate.getForObject("http://example.com", String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplate` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
|
||||
|
||||
TIP: If you see errors such as `java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89`, try injecting `RestOperations` or setting `spring.aop.proxyTargetClass=true`.
|
||||
|
||||
[[multiple-webclient-objects]]
|
||||
== Multiple WebClient Objects
|
||||
|
||||
If you want a `WebClient` that is not load-balanced, create a `WebClient` bean and inject it.
|
||||
To access the load-balanced `WebClient`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
WebClient.Builder loadBalanced() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
WebClient.Builder webClient() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
}
|
||||
|
||||
public class MyClass {
|
||||
@Autowired
|
||||
private WebClient.Builder webClientBuilder;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
private WebClient.Builder loadBalanced;
|
||||
|
||||
public Mono<String> doOtherStuff() {
|
||||
return loadBalanced.build().get().uri("http://stores/stores")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
|
||||
public Mono<String> doStuff() {
|
||||
return webClientBuilder.build().get().uri("http://example.com")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[loadbalanced-webclient]]
|
||||
== Spring WebFlux `WebClient` as a Load Balancer Client
|
||||
|
||||
|
||||
@@ -44,12 +44,13 @@ NOTE: The classes you pass as `@LoadBalancerClient` or `@LoadBalancerClients` co
|
||||
[[spring-cloud-loadbalancer-integrations]]
|
||||
== Spring Cloud LoadBalancer integrations
|
||||
|
||||
In order to make it easy to use Spring Cloud LoadBalancer, we provide `ReactorLoadBalancerExchangeFilterFunction` that can be used with `WebClient` and `BlockingLoadBalancerClient` that works with `RestTemplate`.
|
||||
In order to make it easy to use Spring Cloud LoadBalancer, we provide `ReactorLoadBalancerExchangeFilterFunction` that can be used with `WebClient` and `BlockingLoadBalancerClient` that works with `RestTemplate` and `RestClient`.
|
||||
You can see more information and examples of usage in the following sections:
|
||||
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#rest-template-loadbalancer-client[Spring RestTemplate as a Load Balancer Client]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#webclinet-loadbalancer-client[Spring WebClient as a Load Balancer Client]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#webflux-with-reactive-loadbalancer[Spring WebFlux WebClient with `ReactorLoadBalancerExchangeFilterFunction`]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#rest-template-loadbalancer-client[Spring `RestTemplate` as a LoadBalancer Client]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#rest-client-loadbalancer-client[Spring `RestClient` as a LoadBalancer Client]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#webclinet-loadbalancer-client[Spring `WebClient` as a LoadBalancer Client]
|
||||
* xref:spring-cloud-commons/common-abstractions.adoc#webflux-with-reactive-loadbalancer[Spring `WebFlux WebClient` with `ReactorLoadBalancerExchangeFilterFunction`]
|
||||
|
||||
[[loadbalancer-caching]]
|
||||
== Spring Cloud LoadBalancer Caching
|
||||
@@ -232,7 +233,7 @@ public class CustomLoadBalancerConfiguration {
|
||||
----
|
||||
|
||||
TIP: For the non-reactive stack, create this supplier with the `withBlockingHealthChecks()`.
|
||||
You can also pass your own `WebClient` or `RestTemplate` instance to be used for the checks.
|
||||
You can also pass your own `WebClient`, `RestTemplate` or `RestClient` instance to be used for the checks.
|
||||
|
||||
WARNING: `HealthCheckServiceInstanceListSupplier` has its own caching mechanism based on Reactor Flux `replay()`. Therefore, if it's being used, you may want to skip wrapping that supplier with `CachingServiceInstanceListSupplier`.
|
||||
|
||||
@@ -336,7 +337,7 @@ public class CustomLoadBalancerConfiguration {
|
||||
|
||||
You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request.
|
||||
|
||||
For `RestTemplate`, you need to implement and define `LoadBalancerRequestTransformer` as follows:
|
||||
For `RestTemplate` and `RestClient`, you need to implement and define `LoadBalancerRequestTransformer` as follows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Verifies whether either {@link RestTemplate} or {@link RestClient} class is present.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public final class BlockingRestClassesPresentCondition extends AnyNestedCondition {
|
||||
|
||||
private BlockingRestClassesPresentCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnClass(RestTemplate.class)
|
||||
static class RestTemplatePresent {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnClass(RestClient.class)
|
||||
static class RestClientPresent {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -26,8 +26,8 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
|
||||
* LoadBalancerClient.
|
||||
* Annotation to mark a RestTemplate, RestClient.Builder or WebClient.Builder bean to be
|
||||
* configured to use a LoadBalancerClient.
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -49,7 +50,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(RestTemplate.class)
|
||||
@Conditional(BlockingRestClassesPresentCondition.class)
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
public class LoadBalancerAutoConfiguration {
|
||||
@@ -99,6 +100,14 @@ public class LoadBalancerAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(LoadBalancerInterceptor.class)
|
||||
@ConditionalOnMissingBean
|
||||
LoadBalancerRestClientBuilderBeanPostProcessor lbRestClientPostProcessor(
|
||||
final LoadBalancerInterceptor loadBalancerInterceptor, ApplicationContext context) {
|
||||
return new LoadBalancerRestClientBuilderBeanPostProcessor(loadBalancerInterceptor, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RetryMissingOrDisabledCondition extends AnyNestedCondition {
|
||||
@@ -164,6 +173,14 @@ public class LoadBalancerAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(RetryLoadBalancerInterceptor.class)
|
||||
@ConditionalOnMissingBean
|
||||
LoadBalancerRestClientBuilderBeanPostProcessor lbRestClientPostProcessor(
|
||||
final RetryLoadBalancerInterceptor loadBalancerInterceptor, ApplicationContext context) {
|
||||
return new LoadBalancerRestClientBuilderBeanPostProcessor(loadBalancerInterceptor, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -30,7 +30,7 @@ import org.springframework.http.HttpRequest;
|
||||
public interface LoadBalancerRequestTransformer {
|
||||
|
||||
/**
|
||||
* Order for the load balancer request tranformer.
|
||||
* Order for the load balancer request transformer.
|
||||
*/
|
||||
int DEFAULT_ORDER = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* A {@link BeanPostProcessor} that adds the provided {@link ClientHttpRequestInterceptor}
|
||||
* to all {@link RestClient.Builder} instances annotated with {@link LoadBalanced}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public class LoadBalancerRestClientBuilderBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ClientHttpRequestInterceptor loadBalancerInterceptor;
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
public LoadBalancerRestClientBuilderBeanPostProcessor(ClientHttpRequestInterceptor loadBalancerInterceptor,
|
||||
ApplicationContext context) {
|
||||
this.loadBalancerInterceptor = loadBalancerInterceptor;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof RestClient.Builder) {
|
||||
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
|
||||
return bean;
|
||||
}
|
||||
((RestClient.Builder) bean).requestInterceptor(loadBalancerInterceptor);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,18 +24,19 @@ import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
|
||||
|
||||
@@ -46,49 +47,82 @@ import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoa
|
||||
*/
|
||||
public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
protected ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(LoadBalancerAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void restTemplateGetsLoadBalancerInterceptor() {
|
||||
ConfigurableApplicationContext context = init(OneRestTemplate.class);
|
||||
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
|
||||
void restTemplateGetsLoadBalancerInterceptor() {
|
||||
applicationContextRunner.withUserConfiguration(OneRestTemplate.class).run(context -> {
|
||||
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
|
||||
|
||||
then(restTemplates).isNotNull();
|
||||
then(restTemplates.values()).hasSize(1);
|
||||
RestTemplate restTemplate = restTemplates.values().iterator().next();
|
||||
then(restTemplate).isNotNull();
|
||||
then(restTemplates).isNotNull();
|
||||
then(restTemplates.values()).hasSize(1);
|
||||
RestTemplate restTemplate = restTemplates.values().iterator().next();
|
||||
then(restTemplate).isNotNull();
|
||||
|
||||
assertLoadBalanced(restTemplate);
|
||||
assertLoadBalanced(restTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void restClientBuilderWithLoadBalancerInterceptor() {
|
||||
applicationContextRunner.withUserConfiguration(OneRestClientBuilder.class).run(context -> {
|
||||
final Map<String, RestClient.Builder> restClientBuilders = context.getBeansOfType(RestClient.Builder.class);
|
||||
|
||||
assertThat(restClientBuilders).isNotNull();
|
||||
assertThat(restClientBuilders).hasSize(1);
|
||||
RestClient.Builder restClientBuilder = restClientBuilders.values().iterator().next();
|
||||
assertThat(restClientBuilder).isNotNull();
|
||||
assertLoadBalanced(restClientBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleRestTemplates() {
|
||||
applicationContextRunner.withUserConfiguration(TwoRestTemplatesAndTwoRestClientBuilders.class).run(context -> {
|
||||
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
|
||||
|
||||
then(restTemplates).isNotNull();
|
||||
Collection<RestTemplate> templates = restTemplates.values();
|
||||
then(templates).hasSize(2);
|
||||
|
||||
TwoRestTemplatesAndTwoRestClientBuilders.Two two = context
|
||||
.getBean(TwoRestTemplatesAndTwoRestClientBuilders.Two.class);
|
||||
|
||||
then(two.loadBalanced).isNotNull();
|
||||
assertLoadBalanced(two.loadBalanced);
|
||||
|
||||
then(two.nonLoadBalanced).isNotNull();
|
||||
then(two.nonLoadBalanced.getInterceptors()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleRestClientBuilders() {
|
||||
applicationContextRunner.withUserConfiguration(TwoRestTemplatesAndTwoRestClientBuilders.class).run(context -> {
|
||||
final Map<String, RestClient.Builder> restClientBuilders = context.getBeansOfType(RestClient.Builder.class);
|
||||
|
||||
assertThat(restClientBuilders).isNotNull();
|
||||
assertThat(restClientBuilders.values()).hasSize(2);
|
||||
|
||||
TwoRestTemplatesAndTwoRestClientBuilders.Two two = context
|
||||
.getBean(TwoRestTemplatesAndTwoRestClientBuilders.Two.class);
|
||||
|
||||
assertThat(two.loadBalancedRestClientBuilder).isNotNull();
|
||||
assertLoadBalanced(two.loadBalancedRestClientBuilder);
|
||||
|
||||
assertThat(two.nonLoadBalancedRestClientBuilder).isNotNull();
|
||||
two.nonLoadBalancedRestClientBuilder
|
||||
.requestInterceptors(interceptors -> assertThat(interceptors).isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void assertLoadBalanced(RestClient.Builder restClientBuilder);
|
||||
|
||||
protected abstract void assertLoadBalanced(RestTemplate restTemplate);
|
||||
|
||||
@Test
|
||||
public void multipleRestTemplates() {
|
||||
ConfigurableApplicationContext context = init(TwoRestTemplates.class);
|
||||
final Map<String, RestTemplate> restTemplates = context.getBeansOfType(RestTemplate.class);
|
||||
|
||||
then(restTemplates).isNotNull();
|
||||
Collection<RestTemplate> templates = restTemplates.values();
|
||||
then(templates).hasSize(2);
|
||||
|
||||
TwoRestTemplates.Two two = context.getBean(TwoRestTemplates.Two.class);
|
||||
|
||||
then(two.loadBalanced).isNotNull();
|
||||
assertLoadBalanced(two.loadBalanced);
|
||||
|
||||
then(two.nonLoadBalanced).isNotNull();
|
||||
then(two.nonLoadBalanced.getInterceptors()).isEmpty();
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext init(Class<?> config) {
|
||||
return init(config, "spring.aop.proxyTargetClass=true");
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext init(Class<?> config, String... props) {
|
||||
return new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(props)
|
||||
.sources(config, LoadBalancerAutoConfiguration.class).run();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(BaseConfiguration.class)
|
||||
protected static class OneRestTemplate {
|
||||
|
||||
@LoadBalanced
|
||||
@@ -97,6 +131,23 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(BaseConfiguration.class)
|
||||
protected static class OneRestClientBuilder {
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestClient.Builder loadBalancedRestClientBuilder() {
|
||||
return RestClient.builder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
LoadBalancerClient loadBalancerClient() {
|
||||
return new NoopLoadBalancerClient();
|
||||
@@ -110,8 +161,8 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(OneRestTemplate.class)
|
||||
protected static class TwoRestTemplates {
|
||||
@Import({ OneRestTemplate.class, OneRestClientBuilder.class })
|
||||
protected static class TwoRestTemplatesAndTwoRestClientBuilders {
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
@@ -119,6 +170,12 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
RestClient.Builder restClientBuilder() {
|
||||
return RestClient.builder();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class Two {
|
||||
|
||||
@@ -129,6 +186,13 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
@LoadBalanced
|
||||
RestTemplate loadBalanced;
|
||||
|
||||
@Autowired
|
||||
RestClient.Builder nonLoadBalancedRestClientBuilder;
|
||||
|
||||
@Autowired
|
||||
@LoadBalanced
|
||||
RestClient.Builder loadBalancedRestClientBuilder;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -20,12 +20,15 @@ import java.util.List;
|
||||
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ClassPathExclusions({ "spring-retry-*.jar", "spring-boot-starter-aop-*.jar" })
|
||||
public class LoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAutoConfigurationTests {
|
||||
@@ -38,4 +41,12 @@ public class LoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAuto
|
||||
then(interceptor).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertLoadBalanced(RestClient.Builder restClientBuilder) {
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,15 +20,17 @@ import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class RetryLoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@@ -40,22 +42,48 @@ public class RetryLoadBalancerAutoConfigurationTests extends AbstractLoadBalance
|
||||
then(interceptor).isInstanceOf(RetryLoadBalancerInterceptor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetryDisabled() {
|
||||
ConfigurableApplicationContext context = init(OneRestTemplate.class, "spring.aop.proxyTargetClass=true",
|
||||
"spring.cloud.loadbalancer.retry.enabled=false");
|
||||
List<ClientHttpRequestInterceptor> interceptors = context.getBean(RestTemplate.class).getInterceptors();
|
||||
then(interceptors).hasSize(1);
|
||||
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
|
||||
then(interceptor).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
@Override
|
||||
protected void assertLoadBalanced(RestClient.Builder restClientBuilder) {
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(RetryLoadBalancerInterceptor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultBackOffPolicy() {
|
||||
ConfigurableApplicationContext context = init(OneRestTemplate.class);
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory = context.getBean(LoadBalancedRetryFactory.class);
|
||||
then(loadBalancedRetryFactory).isInstanceOf(LoadBalancedRetryFactory.class);
|
||||
then(loadBalancedRetryFactory.createBackOffPolicy("foo")).isInstanceOf(NoBackOffPolicy.class);
|
||||
void testRetryDisabled() {
|
||||
applicationContextRunner.withUserConfiguration(OneRestTemplate.class)
|
||||
.withPropertyValues("spring.aop.proxyTargetClass=true", "spring.cloud.loadbalancer.retry.enabled=false")
|
||||
.run(context -> {
|
||||
List<ClientHttpRequestInterceptor> interceptors = context.getBean(RestTemplate.class)
|
||||
.getInterceptors();
|
||||
then(interceptors).hasSize(1);
|
||||
ClientHttpRequestInterceptor interceptor = interceptors.get(0);
|
||||
then(interceptor).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRetryDisabledWithRestClientBuilder() {
|
||||
applicationContextRunner.withUserConfiguration(OneRestClientBuilder.class)
|
||||
.withPropertyValues("spring.aop.proxyTargetClass=true", "spring.cloud.loadbalancer.retry.enabled=false")
|
||||
.run(context -> {
|
||||
RestClient.Builder restClientBuilder = context.getBean(RestClient.Builder.class);
|
||||
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultBackOffPolicy() {
|
||||
applicationContextRunner.withUserConfiguration(OneRestTemplate.class).run(context -> {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory = context.getBean(LoadBalancedRetryFactory.class);
|
||||
then(loadBalancedRetryFactory).isInstanceOf(LoadBalancedRetryFactory.class);
|
||||
then(loadBalancedRetryFactory.createBackOffPolicy("foo")).isInstanceOf(NoBackOffPolicy.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -188,6 +189,16 @@ public class LoadBalancerClientConfiguration {
|
||||
.build(context);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean({ DiscoveryClient.class, RestClient.class })
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(HealthCheckConfigurationCondition.class)
|
||||
public ServiceInstanceListSupplier healthCheckRestClientDiscoveryClientServiceInstanceListSupplier(
|
||||
ConfigurableApplicationContext context) {
|
||||
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient()
|
||||
.withBlockingRestClientHealthChecks().build(context);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(DiscoveryClient.class)
|
||||
@ConditionalOnMissingBean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.BlockingRestClassesPresentCondition;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
@@ -35,9 +36,9 @@ import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalance
|
||||
import org.springframework.cloud.loadbalancer.core.LoadBalancerServiceInstanceCookieTransformer;
|
||||
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.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* An autoconfiguration for {@link BlockingLoadBalancerClient}.
|
||||
@@ -50,7 +51,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
@LoadBalancerClients
|
||||
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
|
||||
@AutoConfigureBefore({ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class })
|
||||
@ConditionalOnClass(RestTemplate.class)
|
||||
@Conditional(BlockingRestClassesPresentCondition.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class BlockingLoadBalancerClientAutoConfiguration {
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -203,6 +204,21 @@ public final class ServiceInstanceListSupplierBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link HealthCheckServiceInstanceListSupplier} that uses user-provided
|
||||
* {@link RestClient} instance to the {@link ServiceInstanceListSupplier} hierarchy.
|
||||
* @return the {@link ServiceInstanceListSupplierBuilder} object
|
||||
*/
|
||||
public ServiceInstanceListSupplierBuilder withBlockingRestClientHealthChecks() {
|
||||
DelegateCreator creator = (context, delegate) -> {
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
LoadBalancerClientFactory loadBalancerClientFactory = context.getBean(LoadBalancerClientFactory.class);
|
||||
return blockingHealthCheckServiceInstanceListSupplier(restClient, delegate, loadBalancerClientFactory);
|
||||
};
|
||||
this.creators.add(creator);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link HealthCheckServiceInstanceListSupplier} that uses user-provided
|
||||
* {@link RestTemplate} instance to the {@link ServiceInstanceListSupplier} hierarchy.
|
||||
@@ -218,6 +234,21 @@ public final class ServiceInstanceListSupplierBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link HealthCheckServiceInstanceListSupplier} that uses user-provided
|
||||
* {@link RestClient} instance to the {@link ServiceInstanceListSupplier} hierarchy.
|
||||
* @param restClient a user-provided {@link RestClient} instance
|
||||
* @return the {@link ServiceInstanceListSupplierBuilder} object
|
||||
*/
|
||||
public ServiceInstanceListSupplierBuilder withBlockingHealthChecks(RestClient restClient) {
|
||||
DelegateCreator creator = (context, delegate) -> {
|
||||
LoadBalancerClientFactory loadBalancerClientFactory = context.getBean(LoadBalancerClientFactory.class);
|
||||
return blockingHealthCheckServiceInstanceListSupplier(restClient, delegate, loadBalancerClientFactory);
|
||||
};
|
||||
this.creators.add(creator);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ZonePreferenceServiceInstanceListSupplier} to the
|
||||
* {@link ServiceInstanceListSupplier} hierarchy.
|
||||
@@ -371,6 +402,22 @@ public final class ServiceInstanceListSupplierBuilder {
|
||||
}));
|
||||
}
|
||||
|
||||
private ServiceInstanceListSupplier blockingHealthCheckServiceInstanceListSupplier(RestClient restClient,
|
||||
ServiceInstanceListSupplier delegate, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new HealthCheckServiceInstanceListSupplier(delegate, loadBalancerClientFactory,
|
||||
(serviceInstance, healthCheckPath) -> Mono.defer(() -> {
|
||||
URI uri = UriComponentsBuilder.fromUriString(getUri(serviceInstance, healthCheckPath)).build()
|
||||
.toUri();
|
||||
try {
|
||||
return Mono.just(HttpStatus.OK
|
||||
.equals(restClient.get().uri(uri).retrieve().toBodilessEntity().getStatusCode()));
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
static String getUri(ServiceInstance serviceInstance, String healthCheckPath) {
|
||||
if (StringUtils.hasText(healthCheckPath)) {
|
||||
String path = healthCheckPath.startsWith("/") ? healthCheckPath : "/" + healthCheckPath;
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
@@ -38,6 +43,7 @@ import org.springframework.cloud.loadbalancer.core.ZonePreferenceServiceInstance
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -191,9 +197,10 @@ class LoadBalancerClientConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateBlockingHealthCheckServiceInstanceListSupplier() {
|
||||
blockingDiscoveryClientRunner.withUserConfiguration(RestTemplateTestConfig.class)
|
||||
@ParameterizedTest
|
||||
@MethodSource("blockingConfigurations")
|
||||
void shouldInstantiateBlockingHealthCheckServiceInstanceListSupplier(Class<?> configurationClass) {
|
||||
blockingDiscoveryClientRunner.withUserConfiguration(configurationClass)
|
||||
.withPropertyValues("spring.cloud.loadbalancer.configurations=health-check").run(context -> {
|
||||
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
|
||||
then(supplier).isInstanceOf(HealthCheckServiceInstanceListSupplier.class);
|
||||
@@ -204,8 +211,8 @@ class LoadBalancerClientConfigurationTests {
|
||||
|
||||
@Test
|
||||
void shouldInstantiateBlockingWeightedServiceInstanceListSupplier() {
|
||||
blockingDiscoveryClientRunner.withUserConfiguration(RestTemplateTestConfig.class)
|
||||
.withPropertyValues("spring.cloud.loadbalancer.configurations=weighted").run(context -> {
|
||||
blockingDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=weighted")
|
||||
.run(context -> {
|
||||
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
|
||||
then(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class);
|
||||
ServiceInstanceListSupplier delegate = ((DelegatingServiceInstanceListSupplier) supplier)
|
||||
@@ -216,6 +223,11 @@ class LoadBalancerClientConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<Arguments> blockingConfigurations() {
|
||||
return Stream.of(Arguments.of(RestTemplateTestConfig.class), Arguments.of(RestClientTestConfig.class),
|
||||
Arguments.of(RestTemplateAndRestClientConfig.class));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TestConfig {
|
||||
|
||||
@@ -237,4 +249,29 @@ class LoadBalancerClientConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class RestClientTestConfig {
|
||||
|
||||
@Bean
|
||||
RestClient restClient() {
|
||||
return RestClient.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class RestTemplateAndRestClientConfig {
|
||||
|
||||
@Bean
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestClient restClient() {
|
||||
return RestClient.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2023 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.
|
||||
@@ -23,6 +23,7 @@ import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -48,7 +49,7 @@ class BlockingLoadBalancerClientAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void worksWithoutSpringWeb() {
|
||||
applicationContextRunner.withClassLoader(new FilteredClassLoader(RestTemplate.class))
|
||||
applicationContextRunner.withClassLoader(new FilteredClassLoader(RestTemplate.class, RestClient.class))
|
||||
.run(context -> assertThat(context).doesNotHaveBean(BlockingLoadBalancerClient.class));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.Lists;
|
||||
@@ -29,6 +30,9 @@ import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -47,6 +51,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -79,10 +84,6 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private final WebClient webClient = WebClient.create();
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
private LoadBalancerProperties properties;
|
||||
|
||||
private HealthCheckServiceInstanceListSupplier listSupplier;
|
||||
@@ -109,7 +110,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
false);
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction(webClient));
|
||||
buildLoadBalancerClientFactory(serviceId, properties), webClientHealthCheckFunction());
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
@@ -151,15 +152,17 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldCheckInstanceWithProvidedHealthCheckPathWithRestTemplate() {
|
||||
@ParameterizedTest
|
||||
@MethodSource("healthCheckFunctions")
|
||||
void shouldCheckInstanceWithProvidedHealthCheckPath(
|
||||
BiFunction<ServiceInstance, String, Mono<Boolean>> healthCheckFunction) {
|
||||
String serviceId = "ignored-service";
|
||||
properties.getHealthCheck().getPath().put("ignored-service", "/health");
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1", serviceId, "127.0.0.1", port,
|
||||
false);
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction(restTemplate));
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction);
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
@@ -167,46 +170,16 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldCheckInstanceWithDefaultHealthCheckPath() {
|
||||
String serviceId = "ignored-service";
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1", serviceId, "127.0.0.1", port,
|
||||
false);
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction(webClient));
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
assertThat(alive).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldReturnFalseIfEndpointNotFound() {
|
||||
@ParameterizedTest
|
||||
@MethodSource("healthCheckFunctions")
|
||||
void shouldReturnFalseIfEndpointNotFound(BiFunction<ServiceInstance, String, Mono<Boolean>> healthCheckFunction) {
|
||||
String serviceId = "ignored-service";
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1", serviceId, "127.0.0.1", port,
|
||||
false);
|
||||
properties.getHealthCheck().getPath().put(serviceId, "/test");
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction(webClient));
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
assertThat(alive).isFalse();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldReturnFalseIfEndpointNotFoundWithRestTemplate() {
|
||||
String serviceId = "ignored-service";
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1", serviceId, "127.0.0.1", port,
|
||||
false);
|
||||
properties.getHealthCheck().getPath().put(serviceId, "/test");
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction(restTemplate));
|
||||
buildLoadBalancerClientFactory(serviceId, properties), healthCheckFunction);
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
@@ -232,7 +205,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.doReturn(Mono.just(false)).when(mock).isAlive(serviceInstance2);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -263,7 +236,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.doReturn(Mono.just(true)).when(mock).isAlive(serviceInstance2);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -296,7 +269,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.doReturn(Mono.just(true)).when(mock).isAlive(serviceInstance2);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -327,7 +300,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.doReturn(Mono.error(new RuntimeException("boom"))).when(mock).isAlive(serviceInstance2);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -353,7 +326,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(delegate.getServiceId()).thenReturn(SERVICE_ID);
|
||||
Mockito.when(delegate.get()).thenReturn(Flux.just(Lists.list(serviceInstance1, serviceInstance2)));
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
if (serviceInstance == serviceInstance1) {
|
||||
@@ -381,7 +354,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(delegate.getServiceId()).thenReturn(SERVICE_ID);
|
||||
Mockito.when(delegate.get()).thenReturn(Flux.just(Lists.list(serviceInstance1)));
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return Mono.just(true);
|
||||
@@ -414,7 +387,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.doReturn(Mono.error(new RuntimeException("boom"))).when(mock).isAlive(serviceInstance2);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -443,7 +416,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(mock.isAlive(serviceInstance1)).thenReturn(Mono.never(), Mono.just(true));
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return mock.isAlive(serviceInstance);
|
||||
@@ -475,7 +448,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(delegate.get()).thenReturn(instances);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return Mono.just(true);
|
||||
@@ -510,7 +483,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(delegate.get()).thenReturn(instances);
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return Mono.just(true);
|
||||
@@ -543,7 +516,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
when(delegate.get()).thenReturn(Flux.just(Collections.singletonList(serviceInstance1)))
|
||||
.thenReturn(Flux.just(Collections.singletonList(serviceInstance2)));
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return Mono.just(true);
|
||||
@@ -572,7 +545,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
when(delegate.getServiceId()).thenReturn(SERVICE_ID);
|
||||
when(delegate.get()).thenReturn(Flux.just(Collections.singletonList(serviceInstance1)))
|
||||
.thenReturn(Flux.just(Collections.singletonList(serviceInstance2)));
|
||||
BiFunction<ServiceInstance, String, Mono<Boolean>> healthCheckFunc = healthCheckFunction(webClient);
|
||||
BiFunction<ServiceInstance, String, Mono<Boolean>> healthCheckFunc = webClientHealthCheckFunction();
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunc) {
|
||||
@Override
|
||||
@@ -601,7 +574,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
Mockito.when(delegate.get()).thenReturn(Flux.just(Lists.list(serviceInstance1)));
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient)) {
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction()) {
|
||||
@Override
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
return Mono.just(true);
|
||||
@@ -633,7 +606,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
.doOnSubscribe(subscription -> subscribed.set(true)).doOnCancel(instancesCanceled::incrementAndGet));
|
||||
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(delegate,
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient));
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction());
|
||||
|
||||
listSupplier.afterPropertiesSet();
|
||||
|
||||
@@ -656,7 +629,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
false);
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance),
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), healthCheckFunction(webClient));
|
||||
buildLoadBalancerClientFactory(SERVICE_ID, properties), webClientHealthCheckFunction());
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
@@ -674,7 +647,7 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
port, false);
|
||||
listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSuppliers.from(serviceId, serviceInstance), loadBalancerClientFactory,
|
||||
healthCheckFunction(webClient));
|
||||
webClientHealthCheckFunction());
|
||||
|
||||
listSupplier.isAlive(serviceInstance).block();
|
||||
});
|
||||
@@ -682,6 +655,18 @@ class HealthCheckServiceInstanceListSupplierTests {
|
||||
assertThat(exception).hasMessageContaining("Connection refused: /127.0.0.1:888");
|
||||
}
|
||||
|
||||
private static Stream<Arguments> healthCheckFunctions() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
RestClient restClient = RestClient.create();
|
||||
return Stream.of(Arguments.of(healthCheckFunction(restTemplate)), Arguments.of(healthCheckFunction(restClient)),
|
||||
Arguments.of(webClientHealthCheckFunction()));
|
||||
}
|
||||
|
||||
private static BiFunction<ServiceInstance, String, Mono<Boolean>> webClientHealthCheckFunction() {
|
||||
WebClient webClient = WebClient.create();
|
||||
return healthCheckFunction(webClient);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2023 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.
|
||||
@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -61,4 +62,17 @@ final class ServiceInstanceListSuppliersTestUtils {
|
||||
});
|
||||
}
|
||||
|
||||
static BiFunction<ServiceInstance, String, Mono<Boolean>> healthCheckFunction(RestClient restClient) {
|
||||
return (serviceInstance, healthCheckPath) -> Mono.defer(() -> {
|
||||
URI uri = UriComponentsBuilder.fromUriString(getUri(serviceInstance, healthCheckPath)).build().toUri();
|
||||
try {
|
||||
return Mono.just(
|
||||
HttpStatus.OK.equals(restClient.get().uri(uri).retrieve().toBodilessEntity().getStatusCode()));
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user