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 list) { @@ -84,6 +91,12 @@ private static List unmodifiableCopy(List 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