diff --git a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc index 6468a80f..42688ad6 100644 --- a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc @@ -554,3 +554,14 @@ NOTE: Starting with `4.1.0`, we have introduced the `callGetWithRequestOnDelegat Since `4.0.0`, Spring Cloud LoadBalancer supports Spring AOT transformations and native images. However, to use this feature, you need to explicitly define your `LoadBalancerClient` service IDs. You can do so by using the `value` or `name` attributes of the `@LoadBalancerClient` annotation or as values of the `spring.cloud.loadbalancer.eager-load.clients` property. +== LoadBalancer Integration for Spring Interface Clients AutoConfiguration + +Since `5.0.0`, Spring Cloud LoadBalancer supports https://docs.spring.io/spring-framework/reference/7.0-SNAPSHOT/integration/rest-clients.html#rest-http-interface[Spring Interface Clients] AutoConfiguration through the `LoadBalancerRestClientHttpServiceGroupConfigurer` and `LoadBalancerWebClientHttpServiceGroupConfigurer`. + +For each Interface Client group, if the group `baseUrl` (defined under the +`spring.http.client.service.group.[groupName].base-url` property) is `null`, a `serviceId`-based URL for load-balancing is set up as the `baseUrl`, with `serviceId` resolved from the Interface Client `groupName`. The default scheme for the load-balanced `baseUrl` (`http`, by default) can be set through the `spring.cloud.loadbalancer.interface-clients.default-scheme` property. + +If the group `baseUrl` is `null` or it is already a `serviceId`-based URL, a +`DeferringLoadBalancerInterceptor` instance is picked from the application context for blocking scenarios, and a `DeferringLoadBalancerExchangeFilterFunction` instance for reactive scenarios, and is added to the group's `RestClient.Builder` or `WebClient.Builder` if available, allowing for the requests to be load-balanced. + + diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java index 83b5780a..ffe133da 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java @@ -44,12 +44,15 @@ import static org.mockito.Mockito.when; * * @author Olga Maciaszek-Sharma */ -@SuppressWarnings({"unchecked", "removal"}) +@SuppressWarnings({ "unchecked", "removal" }) class LoadBalancerRestClientHttpServiceGroupConfigurerTests { private static final String GROUP_NAME = "testService"; + private ReactiveLoadBalancer.Factory loadBalancerClientFactory; + private HttpClientServiceProperties clientServiceProperties; + private ObjectProvider interceptorProvider; @BeforeEach @@ -64,39 +67,33 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests { @Test void shouldAddInterceptorWhenBaseUrlIsNotSet() { - LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer( + interceptorProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); groups.builder.requestInterceptors(interceptors -> { - assertThat(interceptors).hasSize(1); - assertThat(interceptors.get(0) - .getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); - } - ); + assertThat(interceptors).hasSize(1); + assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); + }); } -// properties.getInterfaceClients().setDefaultScheme("https"); - @Test void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() { Group group = new Group(); group.setBaseUrl("https://" + GROUP_NAME + "/path"); clientServiceProperties.getGroup().put(GROUP_NAME, group); - LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer( + interceptorProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); groups.builder.requestInterceptors(interceptors -> { - assertThat(interceptors).hasSize(1); - assertThat(interceptors.get(0) - .getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); - } - ); + assertThat(interceptors).hasSize(1); + assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); + }); } @Test @@ -104,24 +101,19 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests { Group group = new Group(); group.setBaseUrl("https://some-other-service/path"); clientServiceProperties.getGroup().put(GROUP_NAME, group); - LoadBalancerRestClientHttpServiceGroupConfigurer configurer = - new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer( + interceptorProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); - groups.builder.requestInterceptors( - interceptors -> assertThat(interceptors).hasSize(0) - ); + groups.builder.requestInterceptors(interceptors -> assertThat(interceptors).hasSize(0)); } - private static class TestGroups implements HttpServiceGroupConfigurer.Groups { RestClient.Builder builder = RestClient.builder(); - @Override public HttpServiceGroupConfigurer.Groups filterByName(String... groupNames) { throw new UnsupportedOperationException("Please, implement me."); @@ -139,24 +131,27 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests { @Override public void configureClient(BiConsumer clientConfigurer) { - clientConfigurer.accept(new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.REST_CLIENT, new HashSet<>()), builder); + clientConfigurer.accept(new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.REST_CLIENT, new HashSet<>()), + builder); } @Override - public void configureProxyFactory(BiConsumer proxyFactoryConfigurer) { + public void configureProxyFactory( + BiConsumer proxyFactoryConfigurer) { } @Override - public void configure(BiConsumer clientConfigurer, BiConsumer proxyFactoryConfigurer) { + public void configure(BiConsumer clientConfigurer, + BiConsumer proxyFactoryConfigurer) { } } private record TestGroup(String name, ClientType clientType, - Set> httpServiceTypes) - implements HttpServiceGroup { + Set> httpServiceTypes) implements HttpServiceGroup { } + } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java index d80cfca0..c91c12a3 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java @@ -44,18 +44,21 @@ import static org.mockito.Mockito.when; * * @author Olga Maciaszek-Sharma */ -@SuppressWarnings({"unchecked", "removal"}) +@SuppressWarnings({ "unchecked", "removal" }) class LoadBalancerWebClientHttpServiceGroupConfigurerTests { private static final String GROUP_NAME = "testService"; + private ReactiveLoadBalancer.Factory loadBalancerClientFactory; + private ReactiveHttpClientServiceProperties clientServiceProperties; + private ObjectProvider> exchangeFilterFunctionProvider; @BeforeEach void setup() { - DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction = - mock(DeferringLoadBalancerExchangeFilterFunction.class); + DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction = mock( + DeferringLoadBalancerExchangeFilterFunction.class); exchangeFilterFunctionProvider = new SimpleObjectProvider<>(exchangeFilterFunction); loadBalancerClientFactory = mock(ReactiveLoadBalancer.Factory.class); clientServiceProperties = new ReactiveHttpClientServiceProperties(); @@ -65,39 +68,33 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests { @Test void shouldAddInterceptorWhenBaseUrlIsNotSet() { - LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer( + exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); groups.builder.filters(filterFunctions -> { - assertThat(filterFunctions).hasSize(1); - assertThat(filterFunctions.get(0) - .getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); - } - ); + assertThat(filterFunctions).hasSize(1); + assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); + }); } -// properties.getInterfaceClients().setDefaultScheme("https"); - @Test void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() { ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group(); group.setBaseUrl("https://" + GROUP_NAME + "/path"); clientServiceProperties.getGroup().put(GROUP_NAME, group); - LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer( + exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); groups.builder.filters(filterFunctions -> { - assertThat(filterFunctions).hasSize(1); - assertThat(filterFunctions.get(0) - .getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); - } - ); + assertThat(filterFunctions).hasSize(1); + assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); + }); } @Test @@ -105,24 +102,19 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests { ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group(); group.setBaseUrl("https://some-other-service/path"); clientServiceProperties.getGroup().put(GROUP_NAME, group); - LoadBalancerWebClientHttpServiceGroupConfigurer configurer = - new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, - clientServiceProperties, loadBalancerClientFactory); + LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer( + exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory); TestGroups groups = new TestGroups(); configurer.configureGroups(groups); - groups.builder.filters( - filterFunctions -> assertThat(filterFunctions).hasSize(0) - ); + groups.builder.filters(filterFunctions -> assertThat(filterFunctions).hasSize(0)); } - private static class TestGroups implements HttpServiceGroupConfigurer.Groups { WebClient.Builder builder = WebClient.builder(); - @Override public HttpServiceGroupConfigurer.Groups filterByName(String... groupNames) { throw new UnsupportedOperationException("Please, implement me."); @@ -140,23 +132,27 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests { @Override public void configureClient(BiConsumer clientConfigurer) { - clientConfigurer.accept(new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.WEB_CLIENT, new HashSet<>()), builder); + clientConfigurer.accept(new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.WEB_CLIENT, new HashSet<>()), + builder); } @Override - public void configureProxyFactory(BiConsumer proxyFactoryConfigurer) { + public void configureProxyFactory( + BiConsumer proxyFactoryConfigurer) { } @Override - public void configure(BiConsumer clientConfigurer, BiConsumer proxyFactoryConfigurer) { + public void configure(BiConsumer clientConfigurer, + BiConsumer proxyFactoryConfigurer) { } + } private record TestGroup(String name, ClientType clientType, - Set> httpServiceTypes) - implements HttpServiceGroup { + Set> httpServiceTypes) implements HttpServiceGroup { } + }