diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
index 320b45f3510b..3cccb6140802 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
@@ -169,18 +169,19 @@ interface Builder {
RSocketRequester.Builder setupMetadata(Object value, @Nullable MimeType mimeType);
/**
- * Provide {@link RSocketStrategies} to use.
- *
By default this is based on default settings of
- * {@link RSocketStrategies.Builder} but may be further customized via
- * {@link #rsocketStrategies(Consumer)}.
+ * Provide the {@link RSocketStrategies} to use.
+ *
This is useful for changing the default settings, yet still allowing
+ * further customizations via {@link #rsocketStrategies(Consumer)}.
+ * If not set, defaults are obtained from {@link RSocketStrategies#builder()}.
+ * @param strategies the strategies to use
*/
RSocketRequester.Builder rsocketStrategies(@Nullable RSocketStrategies strategies);
/**
* Customize the {@link RSocketStrategies}.
- *
By default this starts out as {@link RSocketStrategies#builder()}.
- * However if strategies were {@link #rsocketStrategies(RSocketStrategies) set}
- * explicitly, then they are {@link RSocketStrategies#mutate() mutated}.
+ *
Allows further customization on {@link RSocketStrategies},
+ * mutating them if they were {@link #rsocketStrategies(RSocketStrategies) set},
+ * or starting from {@link RSocketStrategies#builder()} defaults}.
*/
RSocketRequester.Builder rsocketStrategies(Consumer configurer);
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 f314b4455daf..4d5aeca17eb7 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -137,11 +137,24 @@ public WebTestClient.Builder filters(Consumer> filt
}
@Override
+ @Deprecated
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
this.webClientBuilder.exchangeStrategies(strategies);
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);
+ return this;
+ }
+
@Override
public WebTestClient.Builder responseTimeout(Duration timeout) {
this.responseTimeout = timeout;
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 7111b8d5284f..f7a0e78824d8 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
@@ -84,6 +84,7 @@
* perform integration tests on an embedded WebFlux server.
*
* @author Rossen Stoyanchev
+ * @author Brian Clozel
* @since 5.0
* @see StatusAssertions
* @see HeaderAssertions
@@ -443,11 +444,34 @@ interface Builder {
/**
* Configure the {@link ExchangeStrategies} to use.
- * By default {@link ExchangeStrategies#withDefaults()} is used.
+ *
This is useful for changing the default settings, yet still allowing
+ * further customizations via {@link #exchangeStrategies(Consumer)}.
+ * By default {@link ExchangeStrategies#withDefaults()} is used.
* @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}.
+ * @since 5.1.12
+ */
+ Builder exchangeStrategies(Consumer configurer);
+
/**
* Max amount of time to wait for responses.
* By default 5 seconds.
@@ -928,7 +952,7 @@ interface BodyContentSpec {
* @since 5.1
* @see #xpath(String, Map, Object...)
*/
- default XpathAssertions xpath(String expression, Object... args){
+ default XpathAssertions xpath(String expression, Object... args) {
return xpath(expression, null, args);
}
@@ -942,7 +966,7 @@ default XpathAssertions xpath(String expression, Object... args){
* @param args arguments to parameterize the expression
* @since 5.1
*/
- XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args);
+ XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args);
/**
* Assert the response body content with the given {@link Consumer}.
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 db31e97218a4..028d85af381b 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,11 @@ public interface ClientCodecConfigurer extends CodecConfigurer {
@Override
ClientDefaultCodecs defaultCodecs();
+ /**
+ * Clone this {@link ClientCodecConfigurer}.
+ */
+ @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 4e1ca8bd0132..55184522c533 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
@@ -87,6 +87,12 @@ public interface CodecConfigurer {
*/
List> getWriters();
+ /**
+ * Clone this {@link CodecConfigurer}.
+ * @since 5.1.12
+ */
+ CodecConfigurer clone();
+
/**
* Customize or replace the HTTP message readers and writers registered by
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 3c6b367c22ac..6d7c61938849 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
@@ -34,13 +34,14 @@
* client and server specific variants.
*
* @author Rossen Stoyanchev
+ * @author Brian Clozel
* @since 5.0
*/
class BaseCodecConfigurer implements CodecConfigurer {
- private final BaseDefaultCodecs defaultCodecs;
+ protected final BaseDefaultCodecs defaultCodecs;
- private final DefaultCustomCodecs customCodecs = new DefaultCustomCodecs();
+ protected final DefaultCustomCodecs customCodecs;
/**
@@ -50,6 +51,16 @@ class BaseCodecConfigurer implements CodecConfigurer {
BaseCodecConfigurer(BaseDefaultCodecs defaultCodecs) {
Assert.notNull(defaultCodecs, "'defaultCodecs' is required");
this.defaultCodecs = defaultCodecs;
+ this.customCodecs = new DefaultCustomCodecs();
+ }
+
+ /**
+ * Constructor with another {@link BaseCodecConfigurer} to copy
+ * the configuration from.
+ */
+ BaseCodecConfigurer(BaseCodecConfigurer other) {
+ this.defaultCodecs = other.cloneDefaultCodecs();
+ this.customCodecs = new DefaultCustomCodecs(other.customCodecs);
}
@@ -87,6 +98,17 @@ public List> getWriters() {
return getWritersInternal(false);
}
+
+ @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"),
@@ -110,7 +132,7 @@ protected List> getWritersInternal(boolean forMultipart) {
/**
* Default implementation of {@code CustomCodecs}.
*/
- private static final class DefaultCustomCodecs implements CustomCodecs {
+ protected static final class DefaultCustomCodecs implements CustomCodecs {
private final List> typedReaders = new ArrayList<>();
@@ -121,6 +143,16 @@ private static final class DefaultCustomCodecs implements CustomCodecs {
private final List> objectWriters = new ArrayList<>();
+ DefaultCustomCodecs() {
+ }
+
+ DefaultCustomCodecs(DefaultCustomCodecs other) {
+ other.typedReaders.addAll(this.typedReaders);
+ other.typedWriters.addAll(this.typedWriters);
+ other.objectReaders.addAll(this.objectReaders);
+ other.objectWriters.addAll(this.objectWriters);
+ }
+
@Override
public void decoder(Decoder> decoder) {
reader(new DecoderHttpMessageReader<>(decoder));
@@ -143,7 +175,6 @@ public void writer(HttpMessageWriter> writer) {
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
}
-
// Package private accessors...
List> getTypedReaders() {
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 39d76ad0ddbf..1020db44d90b 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
@@ -106,6 +106,21 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
private boolean registerDefaults = true;
+ BaseDefaultCodecs() {
+ }
+
+ protected BaseDefaultCodecs(BaseDefaultCodecs other) {
+ this.jackson2JsonDecoder = other.jackson2JsonDecoder;
+ this.jackson2JsonEncoder = other.jackson2JsonEncoder;
+ this.protobufDecoder = other.protobufDecoder;
+ this.protobufEncoder = other.protobufEncoder;
+ this.jaxb2Decoder = other.jaxb2Decoder;
+ this.jaxb2Encoder = other.jaxb2Encoder;
+ this.maxInMemorySize = other.maxInMemorySize;
+ this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
+ this.registerDefaults = other.registerDefaults;
+ }
+
@Override
public void jackson2JsonDecoder(Decoder> decoder) {
this.jackson2JsonDecoder = decoder;
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java
index 9f578b7320ab..e764cb969612 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,6 +49,17 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
private Supplier>> partWritersSupplier;
+ ClientDefaultCodecsImpl() {
+ }
+
+ ClientDefaultCodecsImpl(ClientDefaultCodecsImpl other) {
+ super(other);
+ this.multipartCodecs = new DefaultMultipartCodecs(other.multipartCodecs);
+ this.sseDecoder = other.sseDecoder;
+ this.partWritersSupplier = other.partWritersSupplier;
+ }
+
+
/**
* Set a supplier for part writers to use when
* {@link #multipartCodecs()} are not explicitly configured.
@@ -73,6 +84,14 @@ public void serverSentEventDecoder(Decoder> decoder) {
this.sseDecoder = decoder;
}
+ @Override
+ public ClientDefaultCodecsImpl clone() {
+ ClientDefaultCodecsImpl codecs = new ClientDefaultCodecsImpl();
+ codecs.multipartCodecs = this.multipartCodecs;
+ codecs.sseDecoder = this.sseDecoder;
+ codecs.partWritersSupplier = this.partWritersSupplier;
+ return codecs;
+ }
@Override
protected void extendObjectReaders(List> objectReaders) {
@@ -116,6 +135,17 @@ private static class DefaultMultipartCodecs implements ClientCodecConfigurer.Mul
private final List> writers = new ArrayList<>();
+
+ DefaultMultipartCodecs() {
+ }
+
+ DefaultMultipartCodecs(@Nullable DefaultMultipartCodecs other) {
+ if (other != null) {
+ this.writers.addAll(other.writers);
+ }
+ }
+
+
@Override
public ClientCodecConfigurer.MultipartCodecs encoder(Encoder> encoder) {
writer(new EncoderHttpMessageWriter<>(encoder));
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
index 9875ded1b98d..737282eecd5e 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,14 +26,30 @@
*/
public class DefaultClientCodecConfigurer extends BaseCodecConfigurer implements ClientCodecConfigurer {
+
public DefaultClientCodecConfigurer() {
super(new ClientDefaultCodecsImpl());
((ClientDefaultCodecsImpl) defaultCodecs()).setPartWritersSupplier(() -> getWritersInternal(true));
}
+ private DefaultClientCodecConfigurer(DefaultClientCodecConfigurer other) {
+ super(other);
+ }
+
+
@Override
public ClientDefaultCodecs defaultCodecs() {
return (ClientDefaultCodecs) super.defaultCodecs();
}
+ @Override
+ public DefaultClientCodecConfigurer clone() {
+ return new DefaultClientCodecConfigurer(this);
+ }
+
+ @Override
+ protected BaseDefaultCodecs cloneDefaultCodecs() {
+ return new ClientDefaultCodecsImpl((ClientDefaultCodecsImpl) defaultCodecs());
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
index 2623d5a7f7b2..661d45d66693 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,13 +26,28 @@
*/
public class DefaultServerCodecConfigurer extends BaseCodecConfigurer implements ServerCodecConfigurer {
+
public DefaultServerCodecConfigurer() {
super(new ServerDefaultCodecsImpl());
}
+ private DefaultServerCodecConfigurer(BaseCodecConfigurer other) {
+ super(other);
+ }
+
+
@Override
public ServerDefaultCodecs defaultCodecs() {
return (ServerDefaultCodecs) super.defaultCodecs();
}
+ @Override
+ public DefaultServerCodecConfigurer clone() {
+ return new DefaultServerCodecConfigurer(this);
+ }
+
+ @Override
+ protected BaseDefaultCodecs cloneDefaultCodecs() {
+ return new ServerDefaultCodecsImpl((ServerDefaultCodecsImpl) defaultCodecs());
+ }
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java
index 37e924cd7e91..1d997c3777b1 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java
@@ -46,6 +46,16 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
private Encoder> sseEncoder;
+ ServerDefaultCodecsImpl() {
+ }
+
+ ServerDefaultCodecsImpl(ServerDefaultCodecsImpl other) {
+ super(other);
+ this.multipartReader = other.multipartReader;
+ this.sseEncoder = other.sseEncoder;
+ }
+
+
@Override
public void multipartReader(HttpMessageReader> reader) {
this.multipartReader = reader;
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 1a27f6400025..16164e24c518 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
@@ -268,6 +268,14 @@ public void encoderDecoderOverrides() {
assertEncoderInstance(jaxb2Encoder);
}
+ @Test
+ public void cloneConfigurer() {
+ CodecConfigurer clone = this.configurer.clone();
+ this.configurer.registerDefaults(false);
+ assertThat(this.configurer.getReaders().size()).isEqualTo(0);
+ assertThat(clone.getReaders().size()).isEqualTo(11);
+ }
+
private Decoder> getNextDecoder(List> readers) {
HttpMessageReader> reader = readers.get(this.index.getAndIncrement());
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 aa1523d9ace5..02b0cc5e5587 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,13 +42,18 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build
}
- private final ClientCodecConfigurer codecConfigurer = ClientCodecConfigurer.create();
+ private final ClientCodecConfigurer codecConfigurer;
public DefaultExchangeStrategiesBuilder() {
+ this.codecConfigurer = ClientCodecConfigurer.create();
this.codecConfigurer.registerDefaults(false);
}
+ private DefaultExchangeStrategiesBuilder(DefaultExchangeStrategies other) {
+ this.codecConfigurer = other.codecConfigurer.clone();
+ }
+
public void defaultConfiguration() {
this.codecConfigurer.registerDefaults(true);
@@ -62,21 +67,23 @@ public ExchangeStrategies.Builder codecs(Consumer consume
@Override
public ExchangeStrategies build() {
- return new DefaultExchangeStrategies(
- this.codecConfigurer.getReaders(), this.codecConfigurer.getWriters());
+ return new DefaultExchangeStrategies(this.codecConfigurer);
}
private static class DefaultExchangeStrategies implements ExchangeStrategies {
+ private final ClientCodecConfigurer codecConfigurer;
+
private final List> readers;
private final List> writers;
- public DefaultExchangeStrategies(List> readers, List> writers) {
- this.readers = unmodifiableCopy(readers);
- this.writers = unmodifiableCopy(writers);
+ public DefaultExchangeStrategies(ClientCodecConfigurer codecConfigurer) {
+ this.codecConfigurer = codecConfigurer;
+ this.readers = unmodifiableCopy(this.codecConfigurer.getReaders());
+ this.writers = unmodifiableCopy(this.codecConfigurer.getWriters());
}
private static List unmodifiableCopy(List extends T> list) {
@@ -84,6 +91,12 @@ private static List unmodifiableCopy(List extends T> list) {
}
+ @Override
+ @Deprecated
+ public Builder mutate() {
+ return new DefaultExchangeStrategiesBuilder(this);
+ }
+
@Override
public List> messageReaders() {
return this.readers;
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 db699ddaae16..82b4c4940861 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
@@ -40,6 +40,7 @@
* Default implementation of {@link WebClient.Builder}.
*
* @author Rossen Stoyanchev
+ * @author Brian Clozel
* @since 5.0
*/
final class DefaultWebClientBuilder implements WebClient.Builder {
@@ -79,14 +80,16 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Nullable
private ClientHttpConnector connector;
- private ExchangeStrategies exchangeStrategies;
+ @Nullable
+ private ExchangeStrategies.Builder strategies;
+
+ private List> strategiesConfigurers;
@Nullable
private ExchangeFunction exchangeFunction;
public DefaultWebClientBuilder() {
- this.exchangeStrategies = ExchangeStrategies.withDefaults();
}
public DefaultWebClientBuilder(DefaultWebClientBuilder other) {
@@ -108,7 +111,7 @@ public DefaultWebClientBuilder(DefaultWebClientBuilder other) {
this.defaultRequest = other.defaultRequest;
this.filters = other.filters != null ? new ArrayList<>(other.filters) : null;
this.connector = other.connector;
- this.exchangeStrategies = other.exchangeStrategies;
+ this.strategies = other.strategies;
this.exchangeFunction = other.exchangeFunction;
}
@@ -203,9 +206,23 @@ public WebClient.Builder clientConnector(ClientHttpConnector connector) {
}
@Override
+ @Deprecated
public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
Assert.notNull(strategies, "ExchangeStrategies must not be null");
- this.exchangeStrategies = strategies;
+ 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;
+ }
+
+ @Override
+ public WebClient.Builder exchangeStrategies(Consumer configurer) {
+ this.strategiesConfigurers.add(configurer);
return this;
}
@@ -229,7 +246,7 @@ public WebClient.Builder clone() {
@Override
public WebClient build() {
ExchangeFunction exchange = (this.exchangeFunction == null ?
- ExchangeFunctions.create(getOrInitConnector(), this.exchangeStrategies) :
+ ExchangeFunctions.create(getOrInitConnector(), initExchangeStrategies()) :
this.exchangeFunction);
ExchangeFunction filteredExchange = (this.filters != null ? this.filters.stream()
.reduce(ExchangeFilterFunction::andThen)
@@ -254,6 +271,19 @@ else if (jettyClientPresent) {
throw new IllegalStateException("No suitable default ClientHttpConnector found");
}
+ @SuppressWarnings("deprecation")
+ private ExchangeStrategies initExchangeStrategies() {
+ if (CollectionUtils.isEmpty(this.strategiesConfigurers)) {
+ return this.strategies != null ? this.strategies.build() : ExchangeStrategies.withDefaults();
+ }
+
+ ExchangeStrategies.Builder builder =
+ this.strategies != null ? this.strategies : ExchangeStrategies.builder();
+
+ this.strategiesConfigurers.forEach(configurer -> configurer.accept(builder));
+ return builder.build();
+ }
+
private UriBuilderFactory initUriBuilderFactory() {
if (this.uriBuilderFactory != null) {
return this.uriBuilderFactory;
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 804fbd9a42fd..dfc2e1e14d59 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
@@ -47,6 +47,18 @@ public interface ExchangeStrategies {
*/
List> messageWriters();
+ /**
+ * 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.");
+ }
+
// Static builder methods
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 57fb6397242a..79fdc5a8c1e4 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
@@ -66,6 +66,7 @@
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Brian Clozel
* @since 5.0
*/
public interface WebClient {
@@ -290,12 +291,35 @@ interface Builder {
Builder clientConnector(ClientHttpConnector connector);
/**
- * Configure the {@link ExchangeStrategies} to use.
- * By default this is obtained from {@link ExchangeStrategies#withDefaults()}.
+ * 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()}.
* @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}.
+ * @since 5.1.12
+ */
+ Builder exchangeStrategies(Consumer configurer);
+
/**
* Provide an {@link ExchangeFunction} pre-configured with
* {@link ClientHttpConnector} and {@link ExchangeStrategies}.
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 eb37921f7a18..af0eeb0f2260 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
@@ -39,4 +39,15 @@ public void withDefaults() {
assertThat(strategies.messageWriters().isEmpty()).isFalse();
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void mutate() {
+ 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 5ef50e1c1903..7963da0087d7 100644
--- a/src/docs/asciidoc/web/webflux-webclient.adoc
+++ b/src/docs/asciidoc/web/webflux-webclient.adoc
@@ -41,28 +41,26 @@ The following example configures < {
- // ...
- })
- .build();
+ Consumer customizeCodecs = builder -> {
+ builder.codecs(configurer -> {
+ //...
+ });
+ };
WebClient client = WebClient.builder()
- .exchangeStrategies(strategies)
+ .exchangeStrategies(customizeCodecs)
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
- val strategies = ExchangeStrategies.builder()
- .codecs {
- // ...
+ val webClient = WebClient.builder()
+ .exchangeStrategies { strategies ->
+ strategies.codecs {
+ //...
+ }
}
.build()
-
- val client = WebClient.builder()
- .exchangeStrategies(strategies)
- .build()
----
Once built, a `WebClient` instance is immutable. However, you can clone it and build a
@@ -95,7 +93,44 @@ modified copy without affecting the original instance, as the following example
// client2 has filterA, filterB, filterC, filterD
----
+[[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:
+
+----
+org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer
+----
+
+You can configure this limit on all default codecs with the following code sample:
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+.Java
+----
+ WebClient webClient = WebClient.builder()
+ .exchangeStrategies(configurer ->
+ configurer.codecs(codecs ->
+ codecs.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
+ )
+ )
+ .build();
+----
+[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
+.Kotlin
+----
+ val webClient = WebClient.builder()
+ .exchangeStrategies { strategies ->
+ strategies.codecs {
+ it.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
+ }
+ }
+ .build()
+----
[[webflux-client-builder-reactor]]
=== Reactor Netty