Allow ExchangeStrategies customizations in WebClient

Backport of d4209392 and acfeb77d

Closes gh-23961
This commit is contained in:
Brian Clozel
2019-11-29 22:26:52 +01:00
committed by Rossen Stoyanchev
parent 59165dd526
commit 83683a13bb
21 changed files with 498 additions and 46 deletions

View File

@@ -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 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build
@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<HttpMessageReader<?>> readers;
private final List<HttpMessageWriter<?>> writers;
public DefaultExchangeStrategies(List<HttpMessageReader<?>> readers, List<HttpMessageWriter<?>> 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 <T> List<T> unmodifiableCopy(List<? extends T> list) {
@@ -93,6 +100,11 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build
public List<HttpMessageWriter<?>> messageWriters() {
return this.writers;
}
@Override
public Builder mutate() {
return new DefaultExchangeStrategiesBuilder(this);
}
}
}

View File

@@ -25,9 +25,11 @@ import java.util.function.Consumer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -38,10 +40,22 @@ import org.springframework.web.util.UriBuilderFactory;
* Default implementation of {@link WebClient.Builder}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
*/
final class DefaultWebClientBuilder implements WebClient.Builder {
private static final boolean reactorClientPresent;
private static final boolean jettyClientPresent;
static {
ClassLoader loader = DefaultWebClientBuilder.class.getClassLoader();
reactorClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader);
jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader);
}
@Nullable
private String baseUrl;
@@ -66,14 +80,17 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Nullable
private ClientHttpConnector connector;
private ExchangeStrategies exchangeStrategies;
@Nullable
private ExchangeStrategies strategies;
@Nullable
private List<Consumer<ExchangeStrategies.Builder>> strategiesConfigurers;
@Nullable
private ExchangeFunction exchangeFunction;
public DefaultWebClientBuilder() {
this.exchangeStrategies = ExchangeStrategies.withDefaults();
}
public DefaultWebClientBuilder(DefaultWebClientBuilder other) {
@@ -95,7 +112,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
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;
}
@@ -191,8 +208,16 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
Assert.notNull(strategies, "ExchangeStrategies must not be null");
this.exchangeStrategies = strategies;
this.strategies = strategies;
return this;
}
@Override
public WebClient.Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer) {
if (this.strategiesConfigurers == null) {
this.strategiesConfigurers = new ArrayList<>(4);
}
this.strategiesConfigurers.add(configurer);
return this;
}
@@ -215,7 +240,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient build() {
ExchangeFunction exchange = initExchangeFunction();
ExchangeFunction exchange = (this.exchangeFunction == null ?
ExchangeFunctions.create(getOrInitConnector(), initExchangeStrategies()) :
this.exchangeFunction);
ExchangeFunction filteredExchange = (this.filters != null ? this.filters.stream()
.reduce(ExchangeFilterFunction::andThen)
.map(filter -> filter.apply(exchange))
@@ -226,16 +253,29 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
this.defaultRequest, new DefaultWebClientBuilder(this));
}
private ExchangeFunction initExchangeFunction() {
if (this.exchangeFunction != null) {
return this.exchangeFunction;
private ClientHttpConnector getOrInitConnector() {
if (this.connector != null) {
return this.connector;
}
else if (this.connector != null) {
return ExchangeFunctions.create(this.connector, this.exchangeStrategies);
else if (reactorClientPresent) {
return new ReactorClientHttpConnector();
}
else {
return ExchangeFunctions.create(new ReactorClientHttpConnector(), this.exchangeStrategies);
else if (jettyClientPresent) {
return new JettyClientHttpConnector();
}
throw new IllegalStateException("No suitable default ClientHttpConnector found");
}
private ExchangeStrategies initExchangeStrategies() {
if (CollectionUtils.isEmpty(this.strategiesConfigurers)) {
return this.strategies != null ? this.strategies : ExchangeStrategies.withDefaults();
}
ExchangeStrategies.Builder builder =
this.strategies != null ? this.strategies.mutate() : ExchangeStrategies.builder();
this.strategiesConfigurers.forEach(configurer -> configurer.accept(builder));
return builder.build();
}
private UriBuilderFactory initUriBuilderFactory() {

View File

@@ -47,6 +47,15 @@ public interface ExchangeStrategies {
*/
List<HttpMessageWriter<?>> messageWriters();
/**
* Return a builder to create a new {@link ExchangeStrategies} instance
* replicated from the current instance.
* @since 5.1.12
*/
default Builder mutate() {
throw new UnsupportedOperationException();
}
// Static builder methods

View File

@@ -64,6 +64,7 @@ import org.springframework.web.util.UriBuilderFactory;
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @author Brian Clozel
* @since 5.0
*/
public interface WebClient {
@@ -289,11 +290,25 @@ public interface WebClient {
/**
* Configure the {@link ExchangeStrategies} to use.
* <p>By default this is obtained from {@link ExchangeStrategies#withDefaults()}.
* <p>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.
* <p>By default this is {@link ExchangeStrategies#withDefaults()}.
* @param strategies the strategies to use
*/
Builder exchangeStrategies(ExchangeStrategies strategies);
/**
* 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<ExchangeStrategies.Builder> configurer);
/**
* Provide an {@link ExchangeFunction} pre-configured with
* {@link ClientHttpConnector} and {@link ExchangeStrategies}.

View File

@@ -39,4 +39,16 @@ public class ExchangeStrategiesTests {
assertFalse(strategies.messageWriters().isEmpty());
}
@Test
@SuppressWarnings("deprecation")
public void mutate() {
ExchangeStrategies strategies = ExchangeStrategies.empty().build();
assertTrue(strategies.messageReaders().isEmpty());
assertTrue(strategies.messageWriters().isEmpty());
ExchangeStrategies mutated = strategies.mutate().codecs(codecs -> codecs.registerDefaults(true)).build();
assertFalse(mutated.messageReaders().isEmpty());
assertFalse(mutated.messageWriters().isEmpty());
}
}