Skip to content

Commit e11cb2d

Browse files
committed
Improve converter config in RestClient.Builder
Internally maintain a chain of HttpMessageConverters.ClientBuilder consumers in addition to the List of converters. List based methods apply to the list. HttpMessageConverters based methods are composed into a Consumer. At build() time prepare a single HttpMessageConverters.ClientBuilder. Insert list based converters first. Apply HttpMessageConverters consumers after that. Deprecate both List methods. Eventually, HttpMessageConverters should be the main mechanism. In the mean time we layer them as described. Closes gh-35578
1 parent d057eb2 commit e11cb2d

File tree

3 files changed

+69
-40
lines changed

3 files changed

+69
-40
lines changed

spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.net.URI;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22-
import java.util.Collections;
2322
import java.util.LinkedHashMap;
2423
import java.util.List;
2524
import java.util.Map;
@@ -111,6 +110,8 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
111110

112111
private @Nullable List<HttpMessageConverter<?>> messageConverters;
113112

113+
private @Nullable Consumer<HttpMessageConverters.ClientBuilder> convertersConfigurer;
114+
114115
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
115116

116117
private @Nullable ClientRequestObservationConvention observationConvention;
@@ -142,6 +143,7 @@ public DefaultRestClientBuilder(DefaultRestClientBuilder other) {
142143
this.initializers = (other.initializers != null) ? new ArrayList<>(other.initializers) : null;
143144
this.requestFactory = other.requestFactory;
144145
this.messageConverters = (other.messageConverters != null ? new ArrayList<>(other.messageConverters) : null);
146+
this.convertersConfigurer = other.convertersConfigurer;
145147
this.observationRegistry = other.observationRegistry;
146148
this.observationConvention = other.observationConvention;
147149
}
@@ -363,25 +365,30 @@ public RestClient.Builder requestFactory(ClientHttpRequestFactory requestFactory
363365
@Override
364366
@SuppressWarnings("removal")
365367
public RestClient.Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer) {
366-
configurer.accept(initMessageConverters());
368+
if (this.messageConverters == null) {
369+
this.messageConverters = new ArrayList<>();
370+
HttpMessageConverters.forClient().registerDefaults().build().forEach(this.messageConverters::add);
371+
}
372+
configurer.accept(this.messageConverters);
367373
validateConverters(this.messageConverters);
368374
return this;
369375
}
370376

371377
@Override
378+
@SuppressWarnings("removal")
372379
public RestClient.Builder messageConverters(Iterable<HttpMessageConverter<?>> messageConverters) {
373380
validateConverters(messageConverters);
374381
List<HttpMessageConverter<?>> converters = new ArrayList<>();
375-
messageConverters.forEach(converter -> converters.add(converter));
376-
this.messageConverters = Collections.unmodifiableList(converters);
382+
messageConverters.forEach(converters::add);
383+
this.messageConverters = converters;
377384
return this;
378385
}
379386

380387
@Override
381388
public RestClient.Builder configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer) {
382-
HttpMessageConverters.ClientBuilder clientBuilder = HttpMessageConverters.forClient();
383-
configurer.accept(clientBuilder);
384-
return messageConverters(clientBuilder.build());
389+
this.convertersConfigurer = (this.convertersConfigurer != null ?
390+
this.convertersConfigurer.andThen(configurer) : configurer);
391+
return this;
385392
}
386393

387394
@Override
@@ -403,20 +410,11 @@ public RestClient.Builder apply(Consumer<RestClient.Builder> builderConsumer) {
403410
return this;
404411
}
405412

406-
@SuppressWarnings("removal")
407-
private List<HttpMessageConverter<?>> initMessageConverters() {
408-
if (this.messageConverters == null) {
409-
this.messageConverters = new ArrayList<>();
410-
HttpMessageConverters.forClient().registerDefaults().build().forEach(this.messageConverters::add);
411-
}
412-
return this.messageConverters;
413-
}
414-
415-
private void validateConverters(@Nullable Iterable<HttpMessageConverter<?>> messageConverters) {
416-
Assert.notNull(messageConverters, "At least one HttpMessageConverter is required");
417-
Assert.isTrue(messageConverters.iterator().hasNext(), "At least one HttpMessageConverter is required");
418-
messageConverters.forEach(converter ->
419-
Assert.notNull(converter, "The HttpMessageConverter list must not contain null elements"));
413+
private void validateConverters(@Nullable Iterable<HttpMessageConverter<?>> converters) {
414+
Assert.notNull(converters, "At least one HttpMessageConverter is required");
415+
Assert.isTrue(converters.iterator().hasNext(), "At least one HttpMessageConverter is required");
416+
converters.forEach(converter -> Assert.notNull(converter,
417+
"The HttpMessageConverter list must not contain null elements"));
420418
}
421419

422420

@@ -427,14 +425,12 @@ public RestClient.Builder clone() {
427425

428426
@Override
429427
public RestClient build() {
428+
430429
ClientHttpRequestFactory requestFactory = initRequestFactory();
431430
UriBuilderFactory uriBuilderFactory = initUriBuilderFactory();
432-
433431
HttpHeaders defaultHeaders = copyDefaultHeaders();
434432
MultiValueMap<String, String> defaultCookies = copyDefaultCookies();
435-
436-
List<HttpMessageConverter<?>> converters =
437-
(this.messageConverters != null ? this.messageConverters : initMessageConverters());
433+
List<HttpMessageConverter<?>> converters = initMessageConverters();
438434

439435
return new DefaultRestClient(
440436
requestFactory, this.interceptors, this.bufferingPredicate, this.initializers,
@@ -495,4 +491,22 @@ private UriBuilderFactory initUriBuilderFactory() {
495491
return CollectionUtils.unmodifiableMultiValueMap(copy);
496492
}
497493

494+
private List<HttpMessageConverter<?>> initMessageConverters() {
495+
HttpMessageConverters.ClientBuilder builder = HttpMessageConverters.forClient();
496+
if (this.messageConverters == null && this.convertersConfigurer == null) {
497+
builder.registerDefaults();
498+
}
499+
else {
500+
if (this.messageConverters != null) {
501+
this.messageConverters.forEach(builder::customMessageConverter);
502+
}
503+
if (this.convertersConfigurer != null) {
504+
this.convertersConfigurer.accept(builder);
505+
}
506+
}
507+
List<HttpMessageConverter<?>> result = new ArrayList<>();
508+
builder.build().forEach(result::add);
509+
return result;
510+
}
511+
498512
}

spring-web/src/main/java/org/springframework/web/client/RestClient.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -449,31 +449,43 @@ Builder defaultStatusHandler(Predicate<HttpStatusCode> statusPredicate,
449449

450450
/**
451451
* Configure the message converters for the {@code RestClient} to use.
452-
* @param configurer the configurer to apply on the list of default
453-
* {@link HttpMessageConverter} pre-initialized
452+
* Multiple consumers are composed together and applied to a single
453+
* {@link HttpMessageConverters.ClientBuilder} instance.
454+
* @param configurer the configurer to apply on an empty {@link HttpMessageConverters.ClientBuilder}.
454455
* @return this builder
455-
* @see #messageConverters(Iterable)
456-
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(Consumer)}
456+
* @since 7.0
457457
*/
458-
@Deprecated(since = "7.0", forRemoval = true)
459-
Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer);
458+
Builder configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer);
460459

461460
/**
462-
* Set the message converters for the {@code RestClient} to use.
463-
* @param messageConverters the list of {@link HttpMessageConverter} to use
461+
* Set the message converters to use.
462+
* <p><strong>Note:</strong> As of 7.0, the converters provided here
463+
* populate a {@link HttpMessageConverters.ClientBuilder} initially, and
464+
* after that the same builder is initialized further through the
465+
* configurers provided via {@link #configureMessageConverters(Consumer)}.
466+
* @param messageConverters the converters to use
464467
* @return this builder
465468
* @since 6.2
466-
* @see #configureMessageConverters(Consumer)
469+
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(Consumer)}
467470
*/
471+
@Deprecated(since = "7.0", forRemoval = true)
468472
Builder messageConverters(Iterable<HttpMessageConverter<?>> messageConverters);
469473

470474
/**
471-
* Configure the message converters for the {@code RestClient} to use.
472-
* @param configurer the configurer to apply on an empty {@link HttpMessageConverters.ClientBuilder}.
475+
* Customize the message converters to use, which is either the default
476+
* converters, or those provided via {@link #messageConverters(Iterable)}.
477+
* The consumer is applied immediately to the internal list
478+
* <p><strong>Note:</strong> As of 7.0, the list of converters customized
479+
* here is used to populate a {@link HttpMessageConverters.ClientBuilder}
480+
* initially, and after that the same builder is initialized further
481+
* through the configurers provided via {@link #configureMessageConverters(Consumer)}.
482+
* @param configurer the configurer to apply on the list of default
483+
* {@link HttpMessageConverter} pre-initialized
473484
* @return this builder
474-
* @since 7.0
485+
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(Consumer)}
475486
*/
476-
Builder configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer);
487+
@Deprecated(since = "7.0", forRemoval = true)
488+
Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer);
477489

478490
/**
479491
* Configure the {@link io.micrometer.observation.ObservationRegistry} to use

spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ void defaultUri() {
112112
assertThat(fieldValue("baseUrl", defaultBuilder)).isEqualTo(baseUrl.toString());
113113
}
114114

115+
@SuppressWarnings("removal")
115116
@Test
116117
void messageConvertersList() {
117118
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
@@ -126,13 +127,15 @@ void messageConvertersList() {
126127
.containsExactly(stringConverter);
127128
}
128129

130+
@SuppressWarnings("removal")
129131
@Test
130132
void messageConvertersListEmpty() {
131133
RestClient.Builder builder = RestClient.builder();
132134
List<HttpMessageConverter<?>> converters = Collections.emptyList();
133135
assertThatIllegalArgumentException().isThrownBy(() -> builder.messageConverters(converters));
134136
}
135137

138+
@SuppressWarnings("removal")
136139
@Test
137140
void messageConvertersListWithNullElement() {
138141
RestClient.Builder builder = RestClient.builder();
@@ -147,9 +150,9 @@ void configureMessageConverters() {
147150
RestClient.Builder builder = RestClient.builder();
148151
builder.configureMessageConverters(clientBuilder -> clientBuilder.stringMessageConverter(stringConverter));
149152
assertThat(builder).isInstanceOf(DefaultRestClientBuilder.class);
150-
DefaultRestClientBuilder defaultBuilder = (DefaultRestClientBuilder) builder;
153+
DefaultRestClient restClient = (DefaultRestClient) builder.build();
151154

152-
assertThat(fieldValue("messageConverters", defaultBuilder))
155+
assertThat(fieldValue("messageConverters", restClient))
153156
.asInstanceOf(InstanceOfAssertFactories.LIST)
154157
.hasExactlyElementsOfTypes(StringHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
155158
}

0 commit comments

Comments
 (0)