Add docs. Reformat.

Signed-off-by: Olga Maciaszek-Sharma <olga.maciaszek-sharma@broadcom.com>
This commit is contained in:
Olga Maciaszek-Sharma
2025-05-14 14:23:30 +02:00
parent 1ef0b0a96a
commit 00ac6b3b41
3 changed files with 64 additions and 62 deletions

View File

@@ -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. 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.

View File

@@ -44,12 +44,15 @@ import static org.mockito.Mockito.when;
* *
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
*/ */
@SuppressWarnings({"unchecked", "removal"}) @SuppressWarnings({ "unchecked", "removal" })
class LoadBalancerRestClientHttpServiceGroupConfigurerTests { class LoadBalancerRestClientHttpServiceGroupConfigurerTests {
private static final String GROUP_NAME = "testService"; private static final String GROUP_NAME = "testService";
private ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory; private ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
private HttpClientServiceProperties clientServiceProperties; private HttpClientServiceProperties clientServiceProperties;
private ObjectProvider<DeferringLoadBalancerInterceptor> interceptorProvider; private ObjectProvider<DeferringLoadBalancerInterceptor> interceptorProvider;
@BeforeEach @BeforeEach
@@ -64,39 +67,33 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests {
@Test @Test
void shouldAddInterceptorWhenBaseUrlIsNotSet() { void shouldAddInterceptorWhenBaseUrlIsNotSet() {
LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
clientServiceProperties, loadBalancerClientFactory); interceptorProvider, clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.requestInterceptors(interceptors -> { groups.builder.requestInterceptors(interceptors -> {
assertThat(interceptors).hasSize(1); assertThat(interceptors).hasSize(1);
assertThat(interceptors.get(0) assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class);
.getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); });
}
);
} }
// properties.getInterfaceClients().setDefaultScheme("https");
@Test @Test
void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() { void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() {
Group group = new Group(); Group group = new Group();
group.setBaseUrl("https://" + GROUP_NAME + "/path"); group.setBaseUrl("https://" + GROUP_NAME + "/path");
clientServiceProperties.getGroup().put(GROUP_NAME, group); clientServiceProperties.getGroup().put(GROUP_NAME, group);
LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
clientServiceProperties, loadBalancerClientFactory); interceptorProvider, clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.requestInterceptors(interceptors -> { groups.builder.requestInterceptors(interceptors -> {
assertThat(interceptors).hasSize(1); assertThat(interceptors).hasSize(1);
assertThat(interceptors.get(0) assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class);
.getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class); });
}
);
} }
@Test @Test
@@ -104,24 +101,19 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests {
Group group = new Group(); Group group = new Group();
group.setBaseUrl("https://some-other-service/path"); group.setBaseUrl("https://some-other-service/path");
clientServiceProperties.getGroup().put(GROUP_NAME, group); clientServiceProperties.getGroup().put(GROUP_NAME, group);
LoadBalancerRestClientHttpServiceGroupConfigurer configurer = LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
new LoadBalancerRestClientHttpServiceGroupConfigurer(interceptorProvider, interceptorProvider, clientServiceProperties, loadBalancerClientFactory);
clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.requestInterceptors( groups.builder.requestInterceptors(interceptors -> assertThat(interceptors).hasSize(0));
interceptors -> assertThat(interceptors).hasSize(0)
);
} }
private static class TestGroups implements HttpServiceGroupConfigurer.Groups<RestClient.Builder> { private static class TestGroups implements HttpServiceGroupConfigurer.Groups<RestClient.Builder> {
RestClient.Builder builder = RestClient.builder(); RestClient.Builder builder = RestClient.builder();
@Override @Override
public HttpServiceGroupConfigurer.Groups<RestClient.Builder> filterByName(String... groupNames) { public HttpServiceGroupConfigurer.Groups<RestClient.Builder> filterByName(String... groupNames) {
throw new UnsupportedOperationException("Please, implement me."); throw new UnsupportedOperationException("Please, implement me.");
@@ -139,24 +131,27 @@ class LoadBalancerRestClientHttpServiceGroupConfigurerTests {
@Override @Override
public void configureClient(BiConsumer<HttpServiceGroup, RestClient.Builder> clientConfigurer) { public void configureClient(BiConsumer<HttpServiceGroup, RestClient.Builder> 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 @Override
public void configureProxyFactory(BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) { public void configureProxyFactory(
BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) {
} }
@Override @Override
public void configure(BiConsumer<HttpServiceGroup, RestClient.Builder> clientConfigurer, BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) { public void configure(BiConsumer<HttpServiceGroup, RestClient.Builder> clientConfigurer,
BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) {
} }
} }
private record TestGroup(String name, ClientType clientType, private record TestGroup(String name, ClientType clientType,
Set<Class<?>> httpServiceTypes) Set<Class<?>> httpServiceTypes) implements HttpServiceGroup {
implements HttpServiceGroup {
} }
} }

View File

@@ -44,18 +44,21 @@ import static org.mockito.Mockito.when;
* *
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
*/ */
@SuppressWarnings({"unchecked", "removal"}) @SuppressWarnings({ "unchecked", "removal" })
class LoadBalancerWebClientHttpServiceGroupConfigurerTests { class LoadBalancerWebClientHttpServiceGroupConfigurerTests {
private static final String GROUP_NAME = "testService"; private static final String GROUP_NAME = "testService";
private ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory; private ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
private ReactiveHttpClientServiceProperties clientServiceProperties; private ReactiveHttpClientServiceProperties clientServiceProperties;
private ObjectProvider<DeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction>> exchangeFilterFunctionProvider; private ObjectProvider<DeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction>> exchangeFilterFunctionProvider;
@BeforeEach @BeforeEach
void setup() { void setup() {
DeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction> exchangeFilterFunction = DeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction> exchangeFilterFunction = mock(
mock(DeferringLoadBalancerExchangeFilterFunction.class); DeferringLoadBalancerExchangeFilterFunction.class);
exchangeFilterFunctionProvider = new SimpleObjectProvider<>(exchangeFilterFunction); exchangeFilterFunctionProvider = new SimpleObjectProvider<>(exchangeFilterFunction);
loadBalancerClientFactory = mock(ReactiveLoadBalancer.Factory.class); loadBalancerClientFactory = mock(ReactiveLoadBalancer.Factory.class);
clientServiceProperties = new ReactiveHttpClientServiceProperties(); clientServiceProperties = new ReactiveHttpClientServiceProperties();
@@ -65,39 +68,33 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests {
@Test @Test
void shouldAddInterceptorWhenBaseUrlIsNotSet() { void shouldAddInterceptorWhenBaseUrlIsNotSet() {
LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
clientServiceProperties, loadBalancerClientFactory); exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.filters(filterFunctions -> { groups.builder.filters(filterFunctions -> {
assertThat(filterFunctions).hasSize(1); assertThat(filterFunctions).hasSize(1);
assertThat(filterFunctions.get(0) assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class);
.getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); });
}
);
} }
// properties.getInterfaceClients().setDefaultScheme("https");
@Test @Test
void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() { void shouldAddInterceptorWhenBaseUrlIsServiceIdUrl() {
ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group(); ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group();
group.setBaseUrl("https://" + GROUP_NAME + "/path"); group.setBaseUrl("https://" + GROUP_NAME + "/path");
clientServiceProperties.getGroup().put(GROUP_NAME, group); clientServiceProperties.getGroup().put(GROUP_NAME, group);
LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
clientServiceProperties, loadBalancerClientFactory); exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.filters(filterFunctions -> { groups.builder.filters(filterFunctions -> {
assertThat(filterFunctions).hasSize(1); assertThat(filterFunctions).hasSize(1);
assertThat(filterFunctions.get(0) assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class);
.getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class); });
}
);
} }
@Test @Test
@@ -105,24 +102,19 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests {
ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group(); ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group();
group.setBaseUrl("https://some-other-service/path"); group.setBaseUrl("https://some-other-service/path");
clientServiceProperties.getGroup().put(GROUP_NAME, group); clientServiceProperties.getGroup().put(GROUP_NAME, group);
LoadBalancerWebClientHttpServiceGroupConfigurer configurer = LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
new LoadBalancerWebClientHttpServiceGroupConfigurer(exchangeFilterFunctionProvider, exchangeFilterFunctionProvider, clientServiceProperties, loadBalancerClientFactory);
clientServiceProperties, loadBalancerClientFactory);
TestGroups groups = new TestGroups(); TestGroups groups = new TestGroups();
configurer.configureGroups(groups); configurer.configureGroups(groups);
groups.builder.filters( groups.builder.filters(filterFunctions -> assertThat(filterFunctions).hasSize(0));
filterFunctions -> assertThat(filterFunctions).hasSize(0)
);
} }
private static class TestGroups implements HttpServiceGroupConfigurer.Groups<WebClient.Builder> { private static class TestGroups implements HttpServiceGroupConfigurer.Groups<WebClient.Builder> {
WebClient.Builder builder = WebClient.builder(); WebClient.Builder builder = WebClient.builder();
@Override @Override
public HttpServiceGroupConfigurer.Groups<WebClient.Builder> filterByName(String... groupNames) { public HttpServiceGroupConfigurer.Groups<WebClient.Builder> filterByName(String... groupNames) {
throw new UnsupportedOperationException("Please, implement me."); throw new UnsupportedOperationException("Please, implement me.");
@@ -140,23 +132,27 @@ class LoadBalancerWebClientHttpServiceGroupConfigurerTests {
@Override @Override
public void configureClient(BiConsumer<HttpServiceGroup, WebClient.Builder> clientConfigurer) { public void configureClient(BiConsumer<HttpServiceGroup, WebClient.Builder> 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 @Override
public void configureProxyFactory(BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) { public void configureProxyFactory(
BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) {
} }
@Override @Override
public void configure(BiConsumer<HttpServiceGroup, WebClient.Builder> clientConfigurer, BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) { public void configure(BiConsumer<HttpServiceGroup, WebClient.Builder> clientConfigurer,
BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) {
} }
} }
private record TestGroup(String name, ClientType clientType, private record TestGroup(String name, ClientType clientType,
Set<Class<?>> httpServiceTypes) Set<Class<?>> httpServiceTypes) implements HttpServiceGroup {
implements HttpServiceGroup {
} }
} }