Set up load-balanced RestClient.Builder with deferring postprocessor.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2024 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.http.client.ClientHttpRequestInterceptor;
|
||||
|
||||
/**
|
||||
* A marker interface for {@link ClientHttpRequestInterceptor} instances used for
|
||||
* load-balancing.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public interface BlockingLoadBalancerInterceptor extends ClientHttpRequestInterceptor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
/**
|
||||
* An {@link ClientHttpRequestInterceptor} implementation that uses {@link ObjectProvider}
|
||||
* to resolve appropriate {@link BlockingLoadBalancerInterceptor} delegate when the
|
||||
* {@link ClientHttpRequestInterceptor#intercept(HttpRequest, byte[], ClientHttpRequestExecution)}
|
||||
* method is first called.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class DeferringLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private final ObjectProvider<BlockingLoadBalancerInterceptor> loadBalancerInterceptorProvider;
|
||||
|
||||
private BlockingLoadBalancerInterceptor delegate;
|
||||
|
||||
public DeferringLoadBalancerInterceptor(
|
||||
ObjectProvider<BlockingLoadBalancerInterceptor> loadBalancerInterceptorProvider) {
|
||||
this.loadBalancerInterceptorProvider = loadBalancerInterceptorProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
tryResolveDelegate();
|
||||
return delegate.intercept(request, body, execution);
|
||||
}
|
||||
|
||||
private void tryResolveDelegate() {
|
||||
if (delegate == null) {
|
||||
delegate = loadBalancerInterceptorProvider.getIfAvailable();
|
||||
if (delegate == null) {
|
||||
throw new IllegalStateException("LoadBalancer interceptor not available.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for tests
|
||||
ObjectProvider<BlockingLoadBalancerInterceptor> getLoadBalancerInterceptorProvider() {
|
||||
return loadBalancerInterceptorProvider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,12 +33,12 @@ 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;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@@ -60,10 +60,6 @@ public class LoadBalancerAutoConfiguration {
|
||||
@Autowired(required = false)
|
||||
private List<RestTemplate> restTemplates = Collections.emptyList();
|
||||
|
||||
@LoadBalanced
|
||||
@Autowired(required = false)
|
||||
private List<RestClient.Builder> restClientBuilders = Collections.emptyList();
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
|
||||
|
||||
@@ -79,26 +75,33 @@ public class LoadBalancerAutoConfiguration {
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SmartInitializingSingleton loadBalancedRestClientBuilderInitializer(
|
||||
ObjectProvider<List<RestClientBuilderCustomizer>> restClientBuilderCustomizers) {
|
||||
return () -> restClientBuilderCustomizers.ifAvailable(customizers -> {
|
||||
for (RestClient.Builder restClientBuilder : restClientBuilders) {
|
||||
for (RestClientBuilderCustomizer customizer : customizers) {
|
||||
customizer.customize(restClientBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
|
||||
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfiguration
|
||||
static class DeferringLoadBalancerInterceptorConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DeferringLoadBalancerInterceptor deferringLoadBalancerInterceptor(
|
||||
ObjectProvider<BlockingLoadBalancerInterceptor> loadBalancerInterceptorObjectProvider) {
|
||||
return new DeferringLoadBalancerInterceptor(loadBalancerInterceptorObjectProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(DeferringLoadBalancerInterceptor.class)
|
||||
@ConditionalOnMissingBean
|
||||
LoadBalancerRestClientBuilderBeanPostProcessor lbRestClientPostProcessor(
|
||||
DeferringLoadBalancerInterceptor loadBalancerInterceptor, ApplicationContext context) {
|
||||
return new LoadBalancerRestClientBuilderBeanPostProcessor(loadBalancerInterceptor, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AutoConfiguration
|
||||
@Conditional(RetryMissingOrDisabledCondition.class)
|
||||
static class LoadBalancerInterceptorConfig {
|
||||
|
||||
@@ -118,13 +121,6 @@ public class LoadBalancerAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RestClientBuilderCustomizer restClientBuilderCustomizer(
|
||||
LoadBalancerInterceptor loadBalancerInterceptor) {
|
||||
return restClientBuilder -> restClientBuilder.requestInterceptor(loadBalancerInterceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RetryMissingOrDisabledCondition extends AnyNestedCondition {
|
||||
@@ -148,7 +144,7 @@ public class LoadBalancerAutoConfiguration {
|
||||
/**
|
||||
* Auto configuration for retry mechanism.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(RetryTemplate.class)
|
||||
public static class RetryAutoConfiguration {
|
||||
|
||||
@@ -189,13 +185,6 @@ public class LoadBalancerAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RestClientBuilderCustomizer restClientBuilderCustomizer(
|
||||
RetryLoadBalancerInterceptor loadBalancerInterceptor) {
|
||||
return restClientBuilder -> restClientBuilder.requestInterceptor(loadBalancerInterceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -31,7 +30,7 @@ import org.springframework.util.Assert;
|
||||
* @author Ryan Baxter
|
||||
* @author William Tran
|
||||
*/
|
||||
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
|
||||
public class LoadBalancerInterceptor implements BlockingLoadBalancerInterceptor {
|
||||
|
||||
private final LoadBalancerClient loadBalancer;
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ import org.springframework.web.client.RestClient;
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.0
|
||||
* @deprecated to be removed in the next release.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class LoadBalancerRestClientBuilderBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ClientHttpRequestInterceptor loadBalancerInterceptor;
|
||||
|
||||
@@ -25,7 +25,9 @@ import org.springframework.web.client.RestClient;
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.1.1
|
||||
* @deprecated to be removed in the next major release.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public interface RestClientBuilderCustomizer {
|
||||
|
||||
void customize(RestClient.Builder restClientBuilder);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -28,7 +28,6 @@ import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
@@ -45,7 +44,7 @@ import org.springframework.util.StreamUtils;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
|
||||
public class RetryLoadBalancerInterceptor implements BlockingLoadBalancerInterceptor {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(RetryLoadBalancerInterceptor.class);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -45,7 +45,10 @@ public class LoadBalancerAutoConfigurationTests extends AbstractLoadBalancerAuto
|
||||
protected void assertLoadBalanced(RestClient.Builder restClientBuilder) {
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(DeferringLoadBalancerInterceptor.class);
|
||||
DeferringLoadBalancerInterceptor interceptor = (DeferringLoadBalancerInterceptor) interceptors.get(0);
|
||||
assertThat(interceptor.getLoadBalancerInterceptorProvider().getObject())
|
||||
.isInstanceOf(LoadBalancerInterceptor.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
@@ -46,7 +46,7 @@ public class RetryLoadBalancerAutoConfigurationTests extends AbstractLoadBalance
|
||||
protected void assertLoadBalanced(RestClient.Builder restClientBuilder) {
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(RetryLoadBalancerInterceptor.class);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(DeferringLoadBalancerInterceptor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ public class RetryLoadBalancerAutoConfigurationTests extends AbstractLoadBalance
|
||||
|
||||
restClientBuilder.requestInterceptors(interceptors -> {
|
||||
assertThat(interceptors).hasSize(1);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(LoadBalancerInterceptor.class);
|
||||
assertThat(interceptors.get(0)).isInstanceOf(DeferringLoadBalancerInterceptor.class);
|
||||
DeferringLoadBalancerInterceptor interceptor = (DeferringLoadBalancerInterceptor) interceptors
|
||||
.get(0);
|
||||
assertThat(interceptor.getLoadBalancerInterceptorProvider().getObject())
|
||||
.isInstanceOf(LoadBalancerInterceptor.class);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user