diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index 4d5aeca17e..5ae951c187 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -143,12 +143,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { return this; } - @Override - public WebTestClient.Builder exchangeStrategies(ExchangeStrategies.Builder strategies) { - this.webClientBuilder.exchangeStrategies(strategies); - return this; - } - @Override public WebTestClient.Builder exchangeStrategies(Consumer configurer) { this.webClientBuilder.exchangeStrategies(configurer); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index f7a0e78824..814a3a9ebd 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -444,30 +444,21 @@ public interface WebTestClient { /** * Configure the {@link ExchangeStrategies} to use. - *

This is useful for changing the default settings, yet still allowing - * further customizations via {@link #exchangeStrategies(Consumer)}. - * By default {@link ExchangeStrategies#withDefaults()} is used. + *

Note that in a scenario where the builder is configured by + * multiple parties, it is preferable to use + * {@link #exchangeStrategies(Consumer)} in order to customize the same + * {@code ExchangeStrategies}. This method here sets the strategies that + * everyone else then can customize. + *

By default this is {@link ExchangeStrategies#withDefaults()}. * @param strategies the strategies to use - * @deprecated as of 5.1 in favor of {@link #exchangeStrategies(ExchangeStrategies.Builder)} */ - @Deprecated Builder exchangeStrategies(ExchangeStrategies strategies); /** - * Configure the {@link ExchangeStrategies.Builder} to use. - *

This is useful for changing the default settings, yet still allowing - * further customizations via {@link #exchangeStrategies(Consumer)}. - * By default {@link ExchangeStrategies#builder()} is used. - * @param strategies the strategies to use - * @since 5.1.12 - */ - Builder exchangeStrategies(ExchangeStrategies.Builder strategies); - - /** - * Customize the {@link ExchangeStrategies}. - *

Allows further customization on {@link ExchangeStrategies}, - * mutating them if they were {@link #exchangeStrategies(ExchangeStrategies) set}, - * or starting from {@link ExchangeStrategies#withDefaults() defaults}. + * Customize the strategies configured via + * {@link #exchangeStrategies(ExchangeStrategies)}. This method is + * designed for use in scenarios where multiple parties wish to update + * the {@code ExchangeStrategies}. * @since 5.1.12 */ Builder exchangeStrategies(Consumer configurer); diff --git a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java index 028d85af38..e41ec73481 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java @@ -64,11 +64,12 @@ public interface ClientCodecConfigurer extends CodecConfigurer { ClientDefaultCodecs defaultCodecs(); /** - * Clone this {@link ClientCodecConfigurer}. + * {@inheritDoc}. */ @Override ClientCodecConfigurer clone(); + /** * Static factory method for a {@code ClientCodecConfigurer}. */ diff --git a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java index 55184522c5..2e69bfc14b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java @@ -88,7 +88,10 @@ public interface CodecConfigurer { List> getWriters(); /** - * Clone this {@link CodecConfigurer}. + * Create a copy of this {@link CodecConfigurer}. The returned clone has its + * own lists of default and custom codecs and generally can be configured + * independently. Keep in mind however that codec instances (if any are + * configured) are themselves not cloned. * @since 5.1.12 */ CodecConfigurer clone(); diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java index 1479390b52..3d49092f59 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java @@ -62,6 +62,12 @@ public interface ServerCodecConfigurer extends CodecConfigurer { @Override ServerDefaultCodecs defaultCodecs(); + /** + * {@inheritDoc}. + */ + @Override + ServerCodecConfigurer clone(); + /** * Static factory method for a {@code ServerCodecConfigurer}. diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java index 6d7c619388..31b3784a3f 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java @@ -37,7 +37,7 @@ import org.springframework.util.Assert; * @author Brian Clozel * @since 5.0 */ -class BaseCodecConfigurer implements CodecConfigurer { +abstract class BaseCodecConfigurer implements CodecConfigurer { protected final BaseDefaultCodecs defaultCodecs; @@ -55,14 +55,21 @@ class BaseCodecConfigurer implements CodecConfigurer { } /** - * Constructor with another {@link BaseCodecConfigurer} to copy - * the configuration from. + * Create a deep copy of the given {@link BaseCodecConfigurer}. + * @since 5.1.12 */ - BaseCodecConfigurer(BaseCodecConfigurer other) { + protected BaseCodecConfigurer(BaseCodecConfigurer other) { this.defaultCodecs = other.cloneDefaultCodecs(); this.customCodecs = new DefaultCustomCodecs(other.customCodecs); } + /** + * Sub-classes should override this to create deep copy of + * {@link BaseDefaultCodecs} which can can be client or server specific. + * @since 5.1.12 + */ + protected abstract BaseDefaultCodecs cloneDefaultCodecs(); + @Override public DefaultCodecs defaultCodecs() { @@ -99,16 +106,6 @@ class BaseCodecConfigurer implements CodecConfigurer { } - @Override - public CodecConfigurer clone() { - return new BaseCodecConfigurer(this); - } - - protected BaseDefaultCodecs cloneDefaultCodecs() { - return new BaseDefaultCodecs(this.defaultCodecs); - } - - /** * Internal method that returns the configured writers. * @param forMultipart whether to returns writers for general use ("false"), @@ -128,6 +125,9 @@ class BaseCodecConfigurer implements CodecConfigurer { return result; } + @Override + public abstract CodecConfigurer clone(); + /** * Default implementation of {@code CustomCodecs}. @@ -146,6 +146,10 @@ class BaseCodecConfigurer implements CodecConfigurer { DefaultCustomCodecs() { } + /** + * Create a deep copy of the given {@link DefaultCustomCodecs}. + * @since 5.1.12 + */ DefaultCustomCodecs(DefaultCustomCodecs other) { other.typedReaders.addAll(this.typedReaders); other.typedWriters.addAll(this.typedWriters); diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index 1020db44d9..ec38c10a29 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -109,6 +109,9 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { BaseDefaultCodecs() { } + /** + * Create a deep copy of the given {@link BaseDefaultCodecs}. + */ protected BaseDefaultCodecs(BaseDefaultCodecs other) { this.jackson2JsonDecoder = other.jackson2JsonDecoder; this.jackson2JsonEncoder = other.jackson2JsonEncoder; diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java index c66fc19eec..5faea0c90e 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; @@ -123,6 +124,47 @@ public class ClientCodecConfigurerTests { .filter(e -> e == decoder).orElse(null)).isSameAs(decoder); } + @Test + public void cloneConfigurer() { + ClientCodecConfigurer clone = this.configurer.clone(); + + Jackson2JsonDecoder jackson2Decoder = new Jackson2JsonDecoder(); + clone.defaultCodecs().serverSentEventDecoder(jackson2Decoder); + clone.defaultCodecs().multipartCodecs().encoder(new Jackson2SmileEncoder()); + clone.defaultCodecs().multipartCodecs().writer(new ResourceHttpMessageWriter()); + + // Clone has the customizations + + Decoder sseDecoder = clone.getReaders().stream() + .filter(reader -> reader instanceof ServerSentEventHttpMessageReader) + .map(reader -> ((ServerSentEventHttpMessageReader) reader).getDecoder()) + .findFirst() + .get(); + + List> multipartWriters = clone.getWriters().stream() + .filter(writer -> writer instanceof MultipartHttpMessageWriter) + .flatMap(writer -> ((MultipartHttpMessageWriter) writer).getPartWriters().stream()) + .collect(Collectors.toList()); + + assertThat(sseDecoder).isSameAs(jackson2Decoder); + assertThat(multipartWriters).hasSize(2); + + // Original does not have the customizations + + sseDecoder = this.configurer.getReaders().stream() + .filter(reader -> reader instanceof ServerSentEventHttpMessageReader) + .map(reader -> ((ServerSentEventHttpMessageReader) reader).getDecoder()) + .findFirst() + .get(); + + multipartWriters = this.configurer.getWriters().stream() + .filter(writer -> writer instanceof MultipartHttpMessageWriter) + .flatMap(writer -> ((MultipartHttpMessageWriter) writer).getPartWriters().stream()) + .collect(Collectors.toList()); + + assertThat(sseDecoder).isNotSameAs(jackson2Decoder); + assertThat(multipartWriters).hasSize(10); + } private Decoder getNextDecoder(List> readers) { HttpMessageReader reader = readers.get(this.index.getAndIncrement()); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index 16164e24c5..1d66e933d8 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -18,6 +18,7 @@ package org.springframework.http.codec.support; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import com.google.protobuf.ExtensionRegistry; import org.junit.jupiter.api.Test; @@ -42,6 +43,8 @@ import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageReader; import org.springframework.http.codec.ResourceHttpMessageWriter; +import org.springframework.http.codec.ServerSentEventHttpMessageReader; +import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2SmileDecoder; @@ -269,11 +272,68 @@ public class CodecConfigurerTests { } @Test - public void cloneConfigurer() { - CodecConfigurer clone = this.configurer.clone(); + public void cloneCustomCodecs() { this.configurer.registerDefaults(false); + CodecConfigurer clone = this.configurer.clone(); + + clone.customCodecs().encoder(new Jackson2JsonEncoder()); + clone.customCodecs().decoder(new Jackson2JsonDecoder()); + clone.customCodecs().reader(new ServerSentEventHttpMessageReader()); + clone.customCodecs().writer(new ServerSentEventHttpMessageWriter()); + assertThat(this.configurer.getReaders().size()).isEqualTo(0); - assertThat(clone.getReaders().size()).isEqualTo(11); + assertThat(this.configurer.getWriters().size()).isEqualTo(0); + assertThat(clone.getReaders().size()).isEqualTo(2); + assertThat(clone.getWriters().size()).isEqualTo(2); + } + + @Test + public void cloneDefaultCodecs() { + CodecConfigurer clone = this.configurer.clone(); + + Jackson2JsonDecoder jacksonDecoder = new Jackson2JsonDecoder(); + Jackson2JsonEncoder jacksonEncoder = new Jackson2JsonEncoder(); + Jaxb2XmlDecoder jaxb2Decoder = new Jaxb2XmlDecoder(); + Jaxb2XmlEncoder jaxb2Encoder = new Jaxb2XmlEncoder(); + ProtobufDecoder protoDecoder = new ProtobufDecoder(); + ProtobufEncoder protoEncoder = new ProtobufEncoder(); + + clone.defaultCodecs().jackson2JsonDecoder(jacksonDecoder); + clone.defaultCodecs().jackson2JsonEncoder(jacksonEncoder); + clone.defaultCodecs().jaxb2Decoder(jaxb2Decoder); + clone.defaultCodecs().jaxb2Encoder(jaxb2Encoder); + clone.defaultCodecs().protobufDecoder(protoDecoder); + clone.defaultCodecs().protobufEncoder(protoEncoder); + + // Clone has the customized the customizations + + List> decoders = clone.getReaders().stream() + .filter(reader -> reader instanceof DecoderHttpMessageReader) + .map(reader -> ((DecoderHttpMessageReader) reader).getDecoder()) + .collect(Collectors.toList()); + + List> encoders = clone.getWriters().stream() + .filter(writer -> writer instanceof EncoderHttpMessageWriter) + .map(reader -> ((EncoderHttpMessageWriter) reader).getEncoder()) + .collect(Collectors.toList()); + + assertThat(decoders).contains(jacksonDecoder, jaxb2Decoder, protoDecoder); + assertThat(encoders).contains(jacksonEncoder, jaxb2Encoder, protoEncoder); + + // Original does not have the customizations + + decoders = this.configurer.getReaders().stream() + .filter(reader -> reader instanceof DecoderHttpMessageReader) + .map(reader -> ((DecoderHttpMessageReader) reader).getDecoder()) + .collect(Collectors.toList()); + + encoders = this.configurer.getWriters().stream() + .filter(writer -> writer instanceof EncoderHttpMessageWriter) + .map(reader -> ((EncoderHttpMessageWriter) reader).getEncoder()) + .collect(Collectors.toList()); + + assertThat(decoders).doesNotContain(jacksonDecoder, jaxb2Decoder, protoDecoder); + assertThat(encoders).doesNotContain(jacksonEncoder, jaxb2Encoder, protoEncoder); } private Decoder getNextDecoder(List> readers) { @@ -324,10 +384,21 @@ public class CodecConfigurerTests { private static class TestCodecConfigurer extends BaseCodecConfigurer { TestCodecConfigurer() { - super(new TestDefaultCodecs()); + super(new BaseDefaultCodecs()); + } + + TestCodecConfigurer(TestCodecConfigurer other) { + super(other); + } + + @Override + protected BaseDefaultCodecs cloneDefaultCodecs() { + return new BaseDefaultCodecs((BaseDefaultCodecs) defaultCodecs()); } - private static class TestDefaultCodecs extends BaseDefaultCodecs { + @Override + public CodecConfigurer clone() { + return new TestCodecConfigurer(this); } } diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java index 5698e154dd..023ec5af9b 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java @@ -154,6 +154,50 @@ public class ServerCodecConfigurerTests { assertThat(((StringDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); } + @Test + public void cloneConfigurer() { + ServerCodecConfigurer clone = this.configurer.clone(); + + MultipartHttpMessageReader reader = new MultipartHttpMessageReader(new SynchronossPartHttpMessageReader()); + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(); + clone.defaultCodecs().multipartReader(reader); + clone.defaultCodecs().serverSentEventEncoder(encoder); + + // Clone has the customizations + + HttpMessageReader actualReader = clone.getReaders().stream() + .filter(r -> r instanceof MultipartHttpMessageReader) + .findFirst() + .get(); + + Encoder actualEncoder = clone.getWriters().stream() + .filter(writer -> writer instanceof ServerSentEventHttpMessageWriter) + .map(writer -> ((ServerSentEventHttpMessageWriter) writer).getEncoder()) + .findFirst() + .get(); + + + assertThat(actualReader).isSameAs(reader); + assertThat(actualEncoder).isSameAs(encoder); + + // Original does not have the customizations + + actualReader = this.configurer.getReaders().stream() + .filter(r -> r instanceof MultipartHttpMessageReader) + .findFirst() + .get(); + + actualEncoder = this.configurer.getWriters().stream() + .filter(writer -> writer instanceof ServerSentEventHttpMessageWriter) + .map(writer -> ((ServerSentEventHttpMessageWriter) writer).getEncoder()) + .findFirst() + .get(); + + + assertThat(actualReader).isNotSameAs(reader); + assertThat(actualEncoder).isNotSameAs(encoder); + } + private Decoder getNextDecoder(List> readers) { HttpMessageReader reader = nextReader(readers); assertThat(reader.getClass()).isEqualTo(DecoderHttpMessageReader.class); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java index a18ae42c68..93732fc2e4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java @@ -91,12 +91,6 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build return Collections.unmodifiableList(new ArrayList<>(list)); } - @Override - @Deprecated - public Builder mutate() { - return new DefaultExchangeStrategiesBuilder(this); - } - @Override public List> messageReaders() { return this.readers; @@ -106,6 +100,11 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build public List> messageWriters() { return this.writers; } + + @Override + public Builder mutate() { + return new DefaultExchangeStrategiesBuilder(this); + } } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index 82b4c49408..f7e9517d27 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -81,8 +81,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder { private ClientHttpConnector connector; @Nullable - private ExchangeStrategies.Builder strategies; + private ExchangeStrategies strategies; + @Nullable private List> strategiesConfigurers; @Nullable @@ -208,13 +209,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @Override @Deprecated public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) { - Assert.notNull(strategies, "ExchangeStrategies must not be null"); - this.strategies = strategies.mutate(); - return this; - } - - @Override - public WebClient.Builder exchangeStrategies(ExchangeStrategies.Builder strategies) { Assert.notNull(strategies, "ExchangeStrategies must not be null"); this.strategies = strategies; return this; @@ -222,6 +216,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @Override public WebClient.Builder exchangeStrategies(Consumer configurer) { + if (this.strategiesConfigurers == null) { + this.strategiesConfigurers = new ArrayList<>(4); + } this.strategiesConfigurers.add(configurer); return this; } @@ -274,11 +271,11 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @SuppressWarnings("deprecation") private ExchangeStrategies initExchangeStrategies() { if (CollectionUtils.isEmpty(this.strategiesConfigurers)) { - return this.strategies != null ? this.strategies.build() : ExchangeStrategies.withDefaults(); + return this.strategies != null ? this.strategies : ExchangeStrategies.withDefaults(); } ExchangeStrategies.Builder builder = - this.strategies != null ? this.strategies : ExchangeStrategies.builder(); + this.strategies != null ? this.strategies.mutate() : ExchangeStrategies.builder(); this.strategiesConfigurers.forEach(configurer -> configurer.accept(builder)); return builder.build(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java index dfc2e1e14d..acf32d0959 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java @@ -51,12 +51,9 @@ public interface ExchangeStrategies { * Return a builder to create a new {@link ExchangeStrategies} instance * replicated from the current instance. * @since 5.1.12 - * @deprecated APIs should consume {@link ExchangeStrategies} as final or accept an - * {@link ExchangeStrategies.Builder builder}. */ - @Deprecated default Builder mutate() { - throw new UnsupportedOperationException("This ExchangeStrategies implementation does not support mutation."); + throw new UnsupportedOperationException(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 79fdc5a8c1..93951bbb96 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -291,31 +291,22 @@ public interface WebClient { Builder clientConnector(ClientHttpConnector connector); /** - * Provide the {@link ExchangeStrategies} to use. - *

This is useful for changing the default settings, yet still allowing - * further customizations via {@link #exchangeStrategies(Consumer)}. - * If not set, defaults are obtained from {@link ExchangeStrategies#withDefaults()}. + * Configure the {@link ExchangeStrategies} to use. + *

Note that in a scenario where the builder is configured by + * multiple parties, it is preferable to use + * {@link #exchangeStrategies(Consumer)} in order to customize the same + * {@code ExchangeStrategies}. This method here sets the strategies that + * everyone else then can customize. + *

By default this is {@link ExchangeStrategies#withDefaults()}. * @param strategies the strategies to use - * @deprecated as of 5.1, in favor of {@link #exchangeStrategies(ExchangeStrategies.Builder)} */ - @Deprecated Builder exchangeStrategies(ExchangeStrategies strategies); /** - * Provide the {@link ExchangeStrategies.Builder} to use. - *

This is useful for changing the default settings, yet still allowing - * further customizations via {@link #exchangeStrategies(Consumer)}. - * If not set, defaults are obtained from {@link ExchangeStrategies#builder()}. - * @param strategies the strategies to use - * @since 5.1.12 - */ - Builder exchangeStrategies(ExchangeStrategies.Builder strategies); - - /** - * Customize the {@link ExchangeStrategies}. - *

Allows further customization on {@link ExchangeStrategies}, - * mutating them if they were {@link #exchangeStrategies(ExchangeStrategies) set}, - * or starting from {@link ExchangeStrategies#withDefaults() defaults}. + * Customize the strategies configured via + * {@link #exchangeStrategies(ExchangeStrategies)}. This method is + * designed for use in scenarios where multiple parties wish to update + * the {@code ExchangeStrategies}. * @since 5.1.12 */ Builder exchangeStrategies(Consumer configurer); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index 7b4a062774..a731aad833 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -20,6 +20,8 @@ import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; import org.junit.jupiter.api.BeforeEach; @@ -36,6 +38,8 @@ import org.springframework.core.NamedThreadLocal; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.codec.FormHttpMessageReader; +import org.springframework.http.codec.FormHttpMessageWriter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java index af0eeb0f22..14b91b1d61 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java @@ -45,6 +45,7 @@ public class ExchangeStrategiesTests { ExchangeStrategies strategies = ExchangeStrategies.empty().build(); assertThat(strategies.messageReaders().isEmpty()).isTrue(); assertThat(strategies.messageWriters().isEmpty()).isTrue(); + ExchangeStrategies mutated = strategies.mutate().codecs(codecs -> codecs.registerDefaults(true)).build(); assertThat(mutated.messageReaders().isEmpty()).isFalse(); assertThat(mutated.messageWriters().isEmpty()).isFalse(); diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 7963da0087..372255fcaf 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -41,14 +41,12 @@ The following example configures < customizeCodecs = builder -> { - builder.codecs(configurer -> { - //... - }); - }; - WebClient client = WebClient.builder() - .exchangeStrategies(customizeCodecs) + .exchangeStrategies(builder -> { + return builder.codecs(codecConfigurer -> { + //... + }); + }) .build(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -96,12 +94,9 @@ modified copy without affecting the original instance, as the following example [[webflux-client-builder-maxinmemorysize]] === MaxInMemorySize -Spring WebFlux configures by default a maximum size for buffering data in-memory when decoding -HTTP responses with the `WebClient`. This avoids application memory issues if the received -response is much larger than expected. - -The default configured value of 256KB might not be enough for your use case, and your application -might hit that limit with the following: +Spring WebFlux configures <> for buffering +data in-memory in codec to avoid application memory issues. By the default this is +configured to 256KB and if that's not enough for your use case, you'll see the following: ---- org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer @@ -113,8 +108,8 @@ You can configure this limit on all default codecs with the following code sampl .Java ---- WebClient webClient = WebClient.builder() - .exchangeStrategies(configurer -> - configurer.codecs(codecs -> + .exchangeStrategies(builder -> + builder.codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) ) ) @@ -124,14 +119,16 @@ You can configure this limit on all default codecs with the following code sampl .Kotlin ---- val webClient = WebClient.builder() - .exchangeStrategies { strategies -> - strategies.codecs { + .exchangeStrategies { builder -> + builder.codecs { it.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) } } .build() ---- + + [[webflux-client-builder-reactor]] === Reactor Netty diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index 92a8108968..cff1be3b71 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -833,7 +833,8 @@ To configure buffer sizes, you can check if a given `Decoder` or `HttpMessageRea exposes a `maxInMemorySize` property and if so the Javadoc will have details about default values. In WebFlux, the `ServerCodecConfigurer` provides a <> from where to set all codecs, through the -`maxInMemorySize` property for default codecs. +`maxInMemorySize` property for default codecs. On the client side, the limit can be changed +in <>. For <> the `maxInMemorySize` property limits the size of non-file parts. For file parts it determines the threshold at which the part