Skip to content

Commit 09105eb

Browse files
committed
Option to supply client builder in HttpServiceGroupConfigurer
Closes gh-35707
1 parent 6dd40a0 commit 09105eb

File tree

3 files changed

+104
-42
lines changed

3 files changed

+104
-42
lines changed

spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceGroupConfigurer.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,30 @@ interface Groups<CB> {
6464
Groups<CB> filter(Predicate<HttpServiceGroup> predicate);
6565

6666
/**
67-
* Configure the client builder for each
68-
* {@link #filter(Predicate) filtered} group.
67+
* Callback to customize the client builder for every group matched by
68+
* the specified name or predicate filters, or to all groups
69+
* if no filters are specified.
6970
*/
7071
void forEachClient(ClientCallback<CB> callback);
7172

7273
/**
73-
* Configure the {@code HttpServiceProxyFactory} for each
74-
* {@link #filter(Predicate) filtered} group.
74+
* Callback to supply the client builder for every group matched by
75+
* the specified name or predicate filters, or to all groups
76+
* if no filters are specified.
77+
*/
78+
void forEachClient(InitializingClientCallback<CB> callback);
79+
80+
/**
81+
* Callback to customize the proxy factory for every group matched by
82+
* the specified name or predicate filters, or to all groups
83+
* if no filters are specified.
7584
*/
7685
void forEachProxyFactory(ProxyFactoryCallback callback);
7786

7887
/**
79-
* Configure the client builder and {@code HttpServiceProxyFactory} for each
80-
* {@link #filter(Predicate) filtered} group.
88+
* Callback to customize the client builder and the proxy factory for
89+
* every group matched by the specified name or predicate filters,
90+
* or to all groups if no filters are specified.
8191
*/
8292
void forEachGroup(GroupCallback<CB> callback);
8393
}
@@ -94,6 +104,17 @@ interface ClientCallback<CB> {
94104
}
95105

96106

107+
/**
108+
* Callback to provide the client builder rather than customize it.
109+
* @param <CB> the type of client builder, i.e. {@code RestClient} or {@code WebClient} builder.
110+
*/
111+
@FunctionalInterface
112+
interface InitializingClientCallback<CB> {
113+
114+
CB initClient(HttpServiceGroup group);
115+
}
116+
117+
97118
/**
98119
* Callback to configure the {@code HttpServiceProxyFactory} for a given group.
99120
*/

spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,16 @@ public HttpServiceProxyRegistry getObject() {
131131

132132
private static class GroupAdapterInitializer {
133133

134-
private static final String REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = "org.springframework.web.client.support.RestClientHttpServiceGroupAdapter";
134+
private static final String REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER =
135+
"org.springframework.web.client.support.RestClientHttpServiceGroupAdapter";
135136

136-
private static final String WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER = "org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupAdapter";
137+
private static final String WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER =
138+
"org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupAdapter";
137139

138140
static Map<HttpServiceGroup.ClientType, HttpServiceGroupAdapter<?>> initGroupAdapters() {
139141
Map<HttpServiceGroup.ClientType, HttpServiceGroupAdapter<?>> map = new LinkedHashMap<>(2);
140-
141142
addGroupAdapter(map, HttpServiceGroup.ClientType.REST_CLIENT, REST_CLIENT_HTTP_SERVICE_GROUP_ADAPTER);
142143
addGroupAdapter(map, HttpServiceGroup.ClientType.WEB_CLIENT, WEB_CLIENT_HTTP_SERVICE_GROUP_ADAPTER);
143-
144144
return map;
145145
}
146146

@@ -167,14 +167,13 @@ private static final class ConfigurableGroup {
167167

168168
private final HttpServiceGroupAdapter<?> groupAdapter;
169169

170-
private final Object clientBuilder;
170+
private @Nullable Object clientBuilder;
171171

172172
private final HttpServiceProxyFactory.Builder proxyFactoryBuilder = HttpServiceProxyFactory.builder();
173173

174174
ConfigurableGroup(HttpServiceGroup group) {
175175
this.group = group;
176176
this.groupAdapter = getGroupAdapter(group.clientType());
177-
this.clientBuilder = this.groupAdapter.createClientBuilder();
178177
}
179178

180179
private static HttpServiceGroupAdapter<?> getGroupAdapter(HttpServiceGroup.ClientType clientType) {
@@ -191,32 +190,39 @@ HttpServiceGroup httpServiceGroup() {
191190
return this.group;
192191
}
193192

194-
@SuppressWarnings("unchecked")
195193
public <CB> void applyClientCallback(HttpServiceGroupConfigurer.ClientCallback<CB> callback) {
196-
callback.withClient(this.group, (CB) this.clientBuilder);
194+
callback.withClient(this.group, getClientBuilder());
195+
}
196+
197+
public <CB> void applyClientCallback(HttpServiceGroupConfigurer.InitializingClientCallback<CB> callback) {
198+
Assert.state(this.clientBuilder == null, "Client builder already initialized");
199+
this.clientBuilder = callback.initClient(this.group);
197200
}
198201

199202
public void applyProxyFactoryCallback(HttpServiceGroupConfigurer.ProxyFactoryCallback callback) {
200203
callback.withProxyFactory(this.group, this.proxyFactoryBuilder);
201204
}
202205

203-
@SuppressWarnings("unchecked")
204206
public <CB> void applyGroupCallback(HttpServiceGroupConfigurer.GroupCallback<CB> callback) {
205-
callback.withGroup(this.group, (CB) this.clientBuilder, this.proxyFactoryBuilder);
207+
callback.withGroup(this.group, getClientBuilder(), this.proxyFactoryBuilder);
208+
}
209+
210+
@SuppressWarnings("unchecked")
211+
private <CB> CB getClientBuilder() {
212+
if (this.clientBuilder == null) {
213+
this.clientBuilder = this.groupAdapter.createClientBuilder();
214+
}
215+
return (CB) this.clientBuilder;
206216
}
207217

208218
public Map<Class<?>, Object> createProxies() {
209219
Map<Class<?>, Object> map = new LinkedHashMap<>(this.group.httpServiceTypes().size());
210-
HttpServiceProxyFactory factory = this.proxyFactoryBuilder.exchangeAdapter(initExchangeAdapter()).build();
220+
HttpExchangeAdapter adapter = this.groupAdapter.createExchangeAdapter(getClientBuilder());
221+
HttpServiceProxyFactory factory = this.proxyFactoryBuilder.exchangeAdapter(adapter).build();
211222
this.group.httpServiceTypes().forEach(type -> map.put(type, factory.createClient(type)));
212223
return map;
213224
}
214225

215-
@SuppressWarnings("unchecked")
216-
private <CB> HttpExchangeAdapter initExchangeAdapter() {
217-
return ((HttpServiceGroupAdapter<CB>) this.groupAdapter).createExchangeAdapter((CB) this.clientBuilder);
218-
}
219-
220226
@Override
221227
public String toString() {
222228
return getClass().getSimpleName() + "[name=" + name() + "]";
@@ -258,6 +264,11 @@ public void forEachClient(HttpServiceGroupConfigurer.ClientCallback<CB> callback
258264
filterAndReset().forEach(group -> group.applyClientCallback(callback));
259265
}
260266

267+
@Override
268+
public void forEachClient(HttpServiceGroupConfigurer.InitializingClientCallback<CB> callback) {
269+
filterAndReset().forEach(group -> group.applyClientCallback(callback));
270+
}
271+
261272
@Override
262273
public void forEachProxyFactory(HttpServiceGroupConfigurer.ProxyFactoryCallback callback) {
263274
filterAndReset().forEach(group -> group.applyProxyFactoryCallback(callback));

spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.util.function.Predicate;
2121

2222
import org.junit.jupiter.api.Test;
23+
import org.mockito.Mockito;
2324

2425
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
26+
import org.springframework.http.client.ClientHttpRequestFactory;
2527
import org.springframework.util.LinkedMultiValueMap;
2628
import org.springframework.util.MultiValueMap;
2729
import org.springframework.web.client.RestClient;
@@ -31,8 +33,14 @@
3133
import org.springframework.web.service.registry.echo.EchoB;
3234
import org.springframework.web.service.registry.greeting.GreetingA;
3335
import org.springframework.web.service.registry.greeting.GreetingB;
36+
import org.springframework.web.testfixture.http.client.MockClientHttpRequest;
37+
import org.springframework.web.testfixture.http.client.MockClientHttpResponse;
3438

3539
import static org.assertj.core.api.Assertions.assertThat;
40+
import static org.mockito.ArgumentMatchers.any;
41+
import static org.mockito.Mockito.verify;
42+
import static org.mockito.Mockito.when;
43+
import static org.springframework.web.service.registry.HttpServiceGroup.ClientType.REST_CLIENT;
3644

3745
/**
3846
* Unit tests for {@link HttpServiceProxyRegistryFactoryBean}.
@@ -42,43 +50,65 @@ public class HttpServiceProxyRegistryFactoryBeanTests {
4250

4351
@Test
4452
void twoGroups() {
45-
46-
GroupsMetadata groupsMetadata = new GroupsMetadata();
47-
4853
String echoName = "echo";
4954
String greetingName = "greeting";
55+
GroupsMetadata groupsMetadata = new GroupsMetadata();
5056

51-
groupsMetadata.getOrCreateGroup(echoName, HttpServiceGroup.ClientType.REST_CLIENT)
52-
.httpServiceTypeNames().addAll(List.of(EchoA.class.getName(), EchoB.class.getName()));
57+
List<String> echoServices = List.of(EchoA.class.getName(), EchoB.class.getName());
58+
groupsMetadata.getOrCreateGroup(echoName, REST_CLIENT).httpServiceTypeNames().addAll(echoServices);
5359

54-
groupsMetadata.getOrCreateGroup(greetingName, HttpServiceGroup.ClientType.REST_CLIENT)
55-
.httpServiceTypeNames().addAll(List.of(GreetingA.class.getName(), GreetingB.class.getName()));
60+
List<String> greetingServices = List.of(GreetingA.class.getName(), GreetingB.class.getName());
61+
groupsMetadata.getOrCreateGroup(greetingName, REST_CLIENT).httpServiceTypeNames().addAll(greetingServices);
5662

5763
Predicate<HttpServiceGroup> echoFilter = group -> group.name().equals(echoName);
5864
Predicate<HttpServiceGroup> greetingFilter = group -> group.name().equals(greetingName);
65+
TestConfigurer groupConfigurer = new TestConfigurer(List.of(echoFilter, greetingFilter));
5966

60-
TestConfigurer testConfigurer = new TestConfigurer(List.of(echoFilter, greetingFilter));
61-
62-
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
63-
applicationContext.registerBean(TestConfigurer.class, () -> testConfigurer);
64-
applicationContext.refresh();
65-
66-
HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata);
67-
factoryBean.setApplicationContext(applicationContext);
68-
factoryBean.setBeanClassLoader(getClass().getClassLoader());
69-
factoryBean.afterPropertiesSet();
70-
71-
HttpServiceProxyRegistry registry = factoryBean.getObject();
67+
HttpServiceProxyRegistry registry = initProxyRegistry(groupConfigurer, groupsMetadata);
7268
assertThat(registry.getGroupNames()).containsExactlyInAnyOrder(echoName, greetingName);
7369
assertThat(registry.getClientTypesInGroup(echoName)).containsExactlyInAnyOrder(EchoA.class, EchoB.class);
7470
assertThat(registry.getClientTypesInGroup(greetingName)).containsExactlyInAnyOrder(GreetingA.class, GreetingB.class);
7571

76-
assertThat(testConfigurer.invocations)
72+
assertThat(groupConfigurer.invocations)
7773
.containsKeys(echoFilter, greetingFilter)
7874
.containsEntry(echoFilter, List.of(echoName))
7975
.containsEntry(greetingFilter, List.of(greetingName));
8076
}
8177

78+
@Test
79+
void initializeClientBuilder() throws Exception {
80+
GroupsMetadata groupsMetadata = new GroupsMetadata();
81+
groupsMetadata.getOrCreateGroup("echo", REST_CLIENT).httpServiceTypeNames().add(EchoA.class.getName());
82+
83+
ClientHttpRequestFactory requestFactory = Mockito.mock(ClientHttpRequestFactory.class);
84+
MockClientHttpRequest request = new MockClientHttpRequest();
85+
request.setResponse(new MockClientHttpResponse());
86+
when(requestFactory.createRequest(any(), any())).thenReturn(request);
87+
88+
RestClient.Builder clientBuilder = RestClient.builder().baseUrl("/").requestFactory(requestFactory);
89+
RestClientHttpServiceGroupConfigurer groupConfigurer = groups -> groups.forEachClient(group -> clientBuilder);
90+
91+
HttpServiceProxyRegistry registry = initProxyRegistry(groupConfigurer, groupsMetadata);
92+
registry.getClient(EchoA.class).handle("foo");
93+
94+
verify(requestFactory);
95+
}
96+
97+
private HttpServiceProxyRegistry initProxyRegistry(
98+
RestClientHttpServiceGroupConfigurer groupConfigurer, GroupsMetadata groupsMetadata) {
99+
100+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
101+
context.registerBean(RestClientHttpServiceGroupConfigurer.class, () -> groupConfigurer);
102+
context.refresh();
103+
104+
HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata);
105+
factoryBean.setApplicationContext(context);
106+
factoryBean.setBeanClassLoader(getClass().getClassLoader());
107+
factoryBean.afterPropertiesSet();
108+
109+
return factoryBean.getObject();
110+
}
111+
82112

83113
private static class TestConfigurer implements RestClientHttpServiceGroupConfigurer {
84114

0 commit comments

Comments
 (0)