Skip to content

Commit 43e047c

Browse files
committed
Allow ExchangeStrategies customizations in WebClient
Prior to this commit, developers could configure their WebClient to use their custom `ExchangeStrategies`, by providing it in the `WebClient.Builder` chain. Once created, an `ExchangeStrategies` instance is not mutable, which makes it hard for further customizations by other components. In the case of the reported issue, other components would override the default configuration for the codecs maxInMemorySize. This commit makes the `ExchangeStrategies` mutable and uses that fact to further customize them with a new `WebClient.Builder#exchangeStrategies` `Consumer` variant. This commit is also deprecating those mutating variants in favor of a new `WebClient.Builder#exchangeStrategies` that takes a `ExchangeStrategies#Builder` directly and avoids mutation issues altogether. Closes gh-24106
1 parent 7fdf775 commit 43e047c

File tree

17 files changed

+322
-31
lines changed

17 files changed

+322
-31
lines changed

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -137,11 +137,24 @@ public WebTestClient.Builder filters(Consumer<List<ExchangeFilterFunction>> filt
137137
}
138138

139139
@Override
140+
@Deprecated
140141
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
141142
this.webClientBuilder.exchangeStrategies(strategies);
142143
return this;
143144
}
144145

146+
@Override
147+
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies.Builder strategies) {
148+
this.webClientBuilder.exchangeStrategies(strategies);
149+
return this;
150+
}
151+
152+
@Override
153+
public WebTestClient.Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer) {
154+
this.webClientBuilder.exchangeStrategies(configurer);
155+
return this;
156+
}
157+
145158
@Override
146159
public WebTestClient.Builder responseTimeout(Duration timeout) {
147160
this.responseTimeout = timeout;

spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

+27-3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* and Spring Kotlin extensions to perform integration tests on an embedded WebFlux server.
7878
*
7979
* @author Rossen Stoyanchev
80+
* @author Brian Clozel
8081
* @since 5.0
8182
* @see StatusAssertions
8283
* @see HeaderAssertions
@@ -436,11 +437,34 @@ interface Builder {
436437

437438
/**
438439
* Configure the {@link ExchangeStrategies} to use.
439-
* <p>By default {@link ExchangeStrategies#withDefaults()} is used.
440+
* <p>This is useful for changing the default settings, yet still allowing
441+
* further customizations via {@link #exchangeStrategies(Consumer)}.
442+
* By default {@link ExchangeStrategies#withDefaults()} is used.
440443
* @param strategies the strategies to use
444+
* @deprecated as of 5.1 in favor of {@link #exchangeStrategies(ExchangeStrategies.Builder)}
441445
*/
446+
@Deprecated
442447
Builder exchangeStrategies(ExchangeStrategies strategies);
443448

449+
/**
450+
* Configure the {@link ExchangeStrategies.Builder} to use.
451+
* <p>This is useful for changing the default settings, yet still allowing
452+
* further customizations via {@link #exchangeStrategies(Consumer)}.
453+
* By default {@link ExchangeStrategies#builder()} is used.
454+
* @param strategies the strategies to use
455+
* @since 5.1.12
456+
*/
457+
Builder exchangeStrategies(ExchangeStrategies.Builder strategies);
458+
459+
/**
460+
* Customize the {@link ExchangeStrategies}.
461+
* <p>Allows further customization on {@link ExchangeStrategies},
462+
* mutating them if they were {@link #exchangeStrategies(ExchangeStrategies) set},
463+
* or starting from {@link ExchangeStrategies#withDefaults() defaults}.
464+
* @since 5.1.12
465+
*/
466+
Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer);
467+
444468
/**
445469
* Max amount of time to wait for responses.
446470
* <p>By default 5 seconds.
@@ -877,7 +901,7 @@ interface BodyContentSpec {
877901
* @since 5.1
878902
* @see #xpath(String, Map, Object...)
879903
*/
880-
default XpathAssertions xpath(String expression, Object... args){
904+
default XpathAssertions xpath(String expression, Object... args) {
881905
return xpath(expression, null, args);
882906
}
883907

@@ -891,7 +915,7 @@ default XpathAssertions xpath(String expression, Object... args){
891915
* @param args arguments to parameterize the expression
892916
* @since 5.1
893917
*/
894-
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);
918+
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);
895919

896920
/**
897921
* Assert the response body content with the given {@link Consumer}.

spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -63,6 +63,11 @@ public interface ClientCodecConfigurer extends CodecConfigurer {
6363
@Override
6464
ClientDefaultCodecs defaultCodecs();
6565

66+
/**
67+
* Clone this {@link ClientCodecConfigurer}.
68+
*/
69+
@Override
70+
ClientCodecConfigurer clone();
6671

6772
/**
6873
* Static factory method for a {@code ClientCodecConfigurer}.

spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java

+6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ public interface CodecConfigurer {
8787
*/
8888
List<HttpMessageWriter<?>> getWriters();
8989

90+
/**
91+
* Clone this {@link CodecConfigurer}.
92+
* @since 5.1.12
93+
*/
94+
CodecConfigurer clone();
95+
9096

9197
/**
9298
* Customize or replace the HTTP message readers and writers registered by

spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java

+35-4
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@
3434
* client and server specific variants.
3535
*
3636
* @author Rossen Stoyanchev
37+
* @author Brian Clozel
3738
* @since 5.0
3839
*/
3940
class BaseCodecConfigurer implements CodecConfigurer {
4041

41-
private final BaseDefaultCodecs defaultCodecs;
42+
protected final BaseDefaultCodecs defaultCodecs;
4243

43-
private final DefaultCustomCodecs customCodecs = new DefaultCustomCodecs();
44+
protected final DefaultCustomCodecs customCodecs;
4445

4546

4647
/**
@@ -50,6 +51,16 @@ class BaseCodecConfigurer implements CodecConfigurer {
5051
BaseCodecConfigurer(BaseDefaultCodecs defaultCodecs) {
5152
Assert.notNull(defaultCodecs, "'defaultCodecs' is required");
5253
this.defaultCodecs = defaultCodecs;
54+
this.customCodecs = new DefaultCustomCodecs();
55+
}
56+
57+
/**
58+
* Constructor with another {@link BaseCodecConfigurer} to copy
59+
* the configuration from.
60+
*/
61+
BaseCodecConfigurer(BaseCodecConfigurer other) {
62+
this.defaultCodecs = other.cloneDefaultCodecs();
63+
this.customCodecs = new DefaultCustomCodecs(other.customCodecs);
5364
}
5465

5566

@@ -87,6 +98,17 @@ public List<HttpMessageWriter<?>> getWriters() {
8798
return getWritersInternal(false);
8899
}
89100

101+
102+
@Override
103+
public CodecConfigurer clone() {
104+
return new BaseCodecConfigurer(this);
105+
}
106+
107+
protected BaseDefaultCodecs cloneDefaultCodecs() {
108+
return new BaseDefaultCodecs(this.defaultCodecs);
109+
}
110+
111+
90112
/**
91113
* Internal method that returns the configured writers.
92114
* @param forMultipart whether to returns writers for general use ("false"),
@@ -110,7 +132,7 @@ protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
110132
/**
111133
* Default implementation of {@code CustomCodecs}.
112134
*/
113-
private static final class DefaultCustomCodecs implements CustomCodecs {
135+
protected static final class DefaultCustomCodecs implements CustomCodecs {
114136

115137
private final List<HttpMessageReader<?>> typedReaders = new ArrayList<>();
116138

@@ -121,6 +143,16 @@ private static final class DefaultCustomCodecs implements CustomCodecs {
121143
private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
122144

123145

146+
DefaultCustomCodecs() {
147+
}
148+
149+
DefaultCustomCodecs(DefaultCustomCodecs other) {
150+
other.typedReaders.addAll(this.typedReaders);
151+
other.typedWriters.addAll(this.typedWriters);
152+
other.objectReaders.addAll(this.objectReaders);
153+
other.objectWriters.addAll(this.objectWriters);
154+
}
155+
124156
@Override
125157
public void decoder(Decoder<?> decoder) {
126158
reader(new DecoderHttpMessageReader<>(decoder));
@@ -143,7 +175,6 @@ public void writer(HttpMessageWriter<?> writer) {
143175
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
144176
}
145177

146-
147178
// Package private accessors...
148179

149180
List<HttpMessageReader<?>> getTypedReaders() {

spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java

+15
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
105105
private boolean registerDefaults = true;
106106

107107

108+
BaseDefaultCodecs() {
109+
}
110+
111+
protected BaseDefaultCodecs(BaseDefaultCodecs other) {
112+
this.jackson2JsonDecoder = other.jackson2JsonDecoder;
113+
this.jackson2JsonEncoder = other.jackson2JsonEncoder;
114+
this.protobufDecoder = other.protobufDecoder;
115+
this.protobufEncoder = other.protobufEncoder;
116+
this.jaxb2Decoder = other.jaxb2Decoder;
117+
this.jaxb2Encoder = other.jaxb2Encoder;
118+
this.maxInMemorySize = other.maxInMemorySize;
119+
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
120+
this.registerDefaults = other.registerDefaults;
121+
}
122+
108123
@Override
109124
public void jackson2JsonDecoder(Decoder<?> decoder) {
110125
this.jackson2JsonDecoder = decoder;

spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,6 +49,17 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
4949
private Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;
5050

5151

52+
ClientDefaultCodecsImpl() {
53+
}
54+
55+
ClientDefaultCodecsImpl(ClientDefaultCodecsImpl other) {
56+
super(other);
57+
this.multipartCodecs = new DefaultMultipartCodecs(other.multipartCodecs);
58+
this.sseDecoder = other.sseDecoder;
59+
this.partWritersSupplier = other.partWritersSupplier;
60+
}
61+
62+
5263
/**
5364
* Set a supplier for part writers to use when
5465
* {@link #multipartCodecs()} are not explicitly configured.
@@ -73,6 +84,14 @@ public void serverSentEventDecoder(Decoder<?> decoder) {
7384
this.sseDecoder = decoder;
7485
}
7586

87+
@Override
88+
public ClientDefaultCodecsImpl clone() {
89+
ClientDefaultCodecsImpl codecs = new ClientDefaultCodecsImpl();
90+
codecs.multipartCodecs = this.multipartCodecs;
91+
codecs.sseDecoder = this.sseDecoder;
92+
codecs.partWritersSupplier = this.partWritersSupplier;
93+
return codecs;
94+
}
7695

7796
@Override
7897
protected void extendObjectReaders(List<HttpMessageReader<?>> objectReaders) {
@@ -116,6 +135,17 @@ private static class DefaultMultipartCodecs implements ClientCodecConfigurer.Mul
116135

117136
private final List<HttpMessageWriter<?>> writers = new ArrayList<>();
118137

138+
139+
DefaultMultipartCodecs() {
140+
}
141+
142+
DefaultMultipartCodecs(@Nullable DefaultMultipartCodecs other) {
143+
if (other != null) {
144+
this.writers.addAll(other.writers);
145+
}
146+
}
147+
148+
119149
@Override
120150
public ClientCodecConfigurer.MultipartCodecs encoder(Encoder<?> encoder) {
121151
writer(new EncoderHttpMessageWriter<>(encoder));

spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,14 +26,30 @@
2626
*/
2727
public class DefaultClientCodecConfigurer extends BaseCodecConfigurer implements ClientCodecConfigurer {
2828

29+
2930
public DefaultClientCodecConfigurer() {
3031
super(new ClientDefaultCodecsImpl());
3132
((ClientDefaultCodecsImpl) defaultCodecs()).setPartWritersSupplier(() -> getWritersInternal(true));
3233
}
3334

35+
private DefaultClientCodecConfigurer(DefaultClientCodecConfigurer other) {
36+
super(other);
37+
}
38+
39+
3440
@Override
3541
public ClientDefaultCodecs defaultCodecs() {
3642
return (ClientDefaultCodecs) super.defaultCodecs();
3743
}
3844

45+
@Override
46+
public DefaultClientCodecConfigurer clone() {
47+
return new DefaultClientCodecConfigurer(this);
48+
}
49+
50+
@Override
51+
protected BaseDefaultCodecs cloneDefaultCodecs() {
52+
return new ClientDefaultCodecsImpl((ClientDefaultCodecsImpl) defaultCodecs());
53+
}
54+
3955
}

spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,13 +26,28 @@
2626
*/
2727
public class DefaultServerCodecConfigurer extends BaseCodecConfigurer implements ServerCodecConfigurer {
2828

29+
2930
public DefaultServerCodecConfigurer() {
3031
super(new ServerDefaultCodecsImpl());
3132
}
3233

34+
private DefaultServerCodecConfigurer(BaseCodecConfigurer other) {
35+
super(other);
36+
}
37+
38+
3339
@Override
3440
public ServerDefaultCodecs defaultCodecs() {
3541
return (ServerDefaultCodecs) super.defaultCodecs();
3642
}
3743

44+
@Override
45+
public DefaultServerCodecConfigurer clone() {
46+
return new DefaultServerCodecConfigurer(this);
47+
}
48+
49+
@Override
50+
protected BaseDefaultCodecs cloneDefaultCodecs() {
51+
return new ServerDefaultCodecsImpl((ServerDefaultCodecsImpl) defaultCodecs());
52+
}
3853
}

spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java

+10
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
4646
private Encoder<?> sseEncoder;
4747

4848

49+
ServerDefaultCodecsImpl() {
50+
}
51+
52+
ServerDefaultCodecsImpl(ServerDefaultCodecsImpl other) {
53+
super(other);
54+
this.multipartReader = other.multipartReader;
55+
this.sseEncoder = other.sseEncoder;
56+
}
57+
58+
4959
@Override
5060
public void multipartReader(HttpMessageReader<?> reader) {
5161
this.multipartReader = reader;

spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ public void encoderDecoderOverrides() {
267267
assertEncoderInstance(jaxb2Encoder);
268268
}
269269

270+
@Test
271+
public void cloneConfigurer() {
272+
CodecConfigurer clone = this.configurer.clone();
273+
this.configurer.registerDefaults(false);
274+
assertEquals(0, this.configurer.getReaders().size());
275+
assertEquals(11, clone.getReaders().size());
276+
}
277+
270278
private Decoder<?> getNextDecoder(List<HttpMessageReader<?>> readers) {
271279
HttpMessageReader<?> reader = readers.get(this.index.getAndIncrement());
272280
assertEquals(DecoderHttpMessageReader.class, reader.getClass());

0 commit comments

Comments
 (0)