diff --git a/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceGroupConfigurer.java b/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceGroupConfigurer.java index b9375081ad6..baa43a1c0c5 100644 --- a/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceGroupConfigurer.java +++ b/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceGroupConfigurer.java @@ -64,20 +64,30 @@ public interface HttpServiceGroupConfigurer extends Ordered { Groups filter(Predicate predicate); /** - * Configure the client builder for each - * {@link #filter(Predicate) filtered} group. + * Callback to customize the client builder for every group matched by + * the specified name or predicate filters, or to all groups + * if no filters are specified. */ void forEachClient(ClientCallback callback); /** - * Configure the {@code HttpServiceProxyFactory} for each - * {@link #filter(Predicate) filtered} group. + * Callback to supply the client builder for every group matched by + * the specified name or predicate filters, or to all groups + * if no filters are specified. + */ + void forEachClient(InitializingClientCallback callback); + + /** + * Callback to customize the proxy factory for every group matched by + * the specified name or predicate filters, or to all groups + * if no filters are specified. */ void forEachProxyFactory(ProxyFactoryCallback callback); /** - * Configure the client builder and {@code HttpServiceProxyFactory} for each - * {@link #filter(Predicate) filtered} group. + * Callback to customize the client builder and the proxy factory for + * every group matched by the specified name or predicate filters, + * or to all groups if no filters are specified. */ void forEachGroup(GroupCallback callback); } @@ -94,6 +104,17 @@ public interface HttpServiceGroupConfigurer extends Ordered { } + /** + * Callback to provide the client builder rather than customize it. + * @param the type of client builder, i.e. {@code RestClient} or {@code WebClient} builder. + */ + @FunctionalInterface + interface InitializingClientCallback { + + CB initClient(HttpServiceGroup group); + } + + /** * Callback to configure the {@code HttpServiceProxyFactory} for a given group. */ diff --git a/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java b/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java index d17ccb53c1d..fb534d63155 100644 --- a/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java @@ -131,16 +131,16 @@ public final class HttpServiceProxyRegistryFactoryBean private static class GroupAdapterInitializer { - private static final String REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = "org.springframework.web.client.support.RestClientHttpServiceGroupAdapter"; + private static final String REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = + "org.springframework.web.client.support.RestClientHttpServiceGroupAdapter"; - private static final String WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = "org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupAdapter"; + private static final String WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = + "org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupAdapter"; static Map> initGroupAdapters() { Map> map = new LinkedHashMap<>(2); - addGroupAdapter(map, HttpServiceGroup.ClientType.REST_CLIENT, REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER); addGroupAdapter(map, HttpServiceGroup.ClientType.WEB_CLIENT, WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER); - return map; } @@ -167,14 +167,13 @@ public final class HttpServiceProxyRegistryFactoryBean private final HttpServiceGroupAdapter groupAdapter; - private final Object clientBuilder; + private @Nullable Object clientBuilder; private final HttpServiceProxyFactory.Builder proxyFactoryBuilder = HttpServiceProxyFactory.builder(); ConfigurableGroup(HttpServiceGroup group) { this.group = group; this.groupAdapter = getGroupAdapter(group.clientType()); - this.clientBuilder = this.groupAdapter.createClientBuilder(); } private static HttpServiceGroupAdapter getGroupAdapter(HttpServiceGroup.ClientType clientType) { @@ -191,32 +190,39 @@ public final class HttpServiceProxyRegistryFactoryBean return this.group; } - @SuppressWarnings("unchecked") public void applyClientCallback(HttpServiceGroupConfigurer.ClientCallback callback) { - callback.withClient(this.group, (CB) this.clientBuilder); + callback.withClient(this.group, getClientBuilder()); + } + + public void applyClientCallback(HttpServiceGroupConfigurer.InitializingClientCallback callback) { + Assert.state(this.clientBuilder == null, "Client builder already initialized"); + this.clientBuilder = callback.initClient(this.group); } public void applyProxyFactoryCallback(HttpServiceGroupConfigurer.ProxyFactoryCallback callback) { callback.withProxyFactory(this.group, this.proxyFactoryBuilder); } - @SuppressWarnings("unchecked") public void applyGroupCallback(HttpServiceGroupConfigurer.GroupCallback callback) { - callback.withGroup(this.group, (CB) this.clientBuilder, this.proxyFactoryBuilder); + callback.withGroup(this.group, getClientBuilder(), this.proxyFactoryBuilder); + } + + @SuppressWarnings("unchecked") + private CB getClientBuilder() { + if (this.clientBuilder == null) { + this.clientBuilder = this.groupAdapter.createClientBuilder(); + } + return (CB) this.clientBuilder; } public Map, Object> createProxies() { Map, Object> map = new LinkedHashMap<>(this.group.httpServiceTypes().size()); - HttpServiceProxyFactory factory = this.proxyFactoryBuilder.exchangeAdapter(initExchangeAdapter()).build(); + HttpExchangeAdapter adapter = this.groupAdapter.createExchangeAdapter(getClientBuilder()); + HttpServiceProxyFactory factory = this.proxyFactoryBuilder.exchangeAdapter(adapter).build(); this.group.httpServiceTypes().forEach(type -> map.put(type, factory.createClient(type))); return map; } - @SuppressWarnings("unchecked") - private HttpExchangeAdapter initExchangeAdapter() { - return ((HttpServiceGroupAdapter) this.groupAdapter).createExchangeAdapter((CB) this.clientBuilder); - } - @Override public String toString() { return getClass().getSimpleName() + "[name=" + name() + "]"; @@ -258,6 +264,11 @@ public final class HttpServiceProxyRegistryFactoryBean filterAndReset().forEach(group -> group.applyClientCallback(callback)); } + @Override + public void forEachClient(HttpServiceGroupConfigurer.InitializingClientCallback callback) { + filterAndReset().forEach(group -> group.applyClientCallback(callback)); + } + @Override public void forEachProxyFactory(HttpServiceGroupConfigurer.ProxyFactoryCallback callback) { filterAndReset().forEach(group -> group.applyProxyFactoryCallback(callback)); diff --git a/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java index e39f1616123..2dc95692878 100644 --- a/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java @@ -20,8 +20,10 @@ import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; @@ -31,8 +33,14 @@ import org.springframework.web.service.registry.echo.EchoA; import org.springframework.web.service.registry.echo.EchoB; import org.springframework.web.service.registry.greeting.GreetingA; import org.springframework.web.service.registry.greeting.GreetingB; +import org.springframework.web.testfixture.http.client.MockClientHttpRequest; +import org.springframework.web.testfixture.http.client.MockClientHttpResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.web.service.registry.HttpServiceGroup.ClientType.REST_CLIENT; /** * Unit tests for {@link HttpServiceProxyRegistryFactoryBean}. @@ -42,43 +50,65 @@ public class HttpServiceProxyRegistryFactoryBeanTests { @Test void twoGroups() { - - GroupsMetadata groupsMetadata = new GroupsMetadata(); - String echoName = "echo"; String greetingName = "greeting"; + GroupsMetadata groupsMetadata = new GroupsMetadata(); - groupsMetadata.getOrCreateGroup(echoName, HttpServiceGroup.ClientType.REST_CLIENT) - .httpServiceTypeNames().addAll(List.of(EchoA.class.getName(), EchoB.class.getName())); + List echoServices = List.of(EchoA.class.getName(), EchoB.class.getName()); + groupsMetadata.getOrCreateGroup(echoName, REST_CLIENT).httpServiceTypeNames().addAll(echoServices); - groupsMetadata.getOrCreateGroup(greetingName, HttpServiceGroup.ClientType.REST_CLIENT) - .httpServiceTypeNames().addAll(List.of(GreetingA.class.getName(), GreetingB.class.getName())); + List greetingServices = List.of(GreetingA.class.getName(), GreetingB.class.getName()); + groupsMetadata.getOrCreateGroup(greetingName, REST_CLIENT).httpServiceTypeNames().addAll(greetingServices); Predicate echoFilter = group -> group.name().equals(echoName); Predicate greetingFilter = group -> group.name().equals(greetingName); + TestConfigurer groupConfigurer = new TestConfigurer(List.of(echoFilter, greetingFilter)); - TestConfigurer testConfigurer = new TestConfigurer(List.of(echoFilter, greetingFilter)); - - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - applicationContext.registerBean(TestConfigurer.class, () -> testConfigurer); - applicationContext.refresh(); - - HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata); - factoryBean.setApplicationContext(applicationContext); - factoryBean.setBeanClassLoader(getClass().getClassLoader()); - factoryBean.afterPropertiesSet(); - - HttpServiceProxyRegistry registry = factoryBean.getObject(); + HttpServiceProxyRegistry registry = initProxyRegistry(groupConfigurer, groupsMetadata); assertThat(registry.getGroupNames()).containsExactlyInAnyOrder(echoName, greetingName); assertThat(registry.getClientTypesInGroup(echoName)).containsExactlyInAnyOrder(EchoA.class, EchoB.class); assertThat(registry.getClientTypesInGroup(greetingName)).containsExactlyInAnyOrder(GreetingA.class, GreetingB.class); - assertThat(testConfigurer.invocations) + assertThat(groupConfigurer.invocations) .containsKeys(echoFilter, greetingFilter) .containsEntry(echoFilter, List.of(echoName)) .containsEntry(greetingFilter, List.of(greetingName)); } + @Test + void initializeClientBuilder() throws Exception { + GroupsMetadata groupsMetadata = new GroupsMetadata(); + groupsMetadata.getOrCreateGroup("echo", REST_CLIENT).httpServiceTypeNames().add(EchoA.class.getName()); + + ClientHttpRequestFactory requestFactory = Mockito.mock(ClientHttpRequestFactory.class); + MockClientHttpRequest request = new MockClientHttpRequest(); + request.setResponse(new MockClientHttpResponse()); + when(requestFactory.createRequest(any(), any())).thenReturn(request); + + RestClient.Builder clientBuilder = RestClient.builder().baseUrl("/").requestFactory(requestFactory); + RestClientHttpServiceGroupConfigurer groupConfigurer = groups -> groups.forEachClient(group -> clientBuilder); + + HttpServiceProxyRegistry registry = initProxyRegistry(groupConfigurer, groupsMetadata); + registry.getClient(EchoA.class).handle("foo"); + + verify(requestFactory); + } + + private HttpServiceProxyRegistry initProxyRegistry( + RestClientHttpServiceGroupConfigurer groupConfigurer, GroupsMetadata groupsMetadata) { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.registerBean(RestClientHttpServiceGroupConfigurer.class, () -> groupConfigurer); + context.refresh(); + + HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata); + factoryBean.setApplicationContext(context); + factoryBean.setBeanClassLoader(getClass().getClassLoader()); + factoryBean.afterPropertiesSet(); + + return factoryBean.getObject(); + } + private static class TestConfigurer implements RestClientHttpServiceGroupConfigurer {