From 82a34f4b246537429a8b236f0d1790b1ae924091 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 2 Feb 2017 16:15:44 -0500 Subject: [PATCH] Update WebClient builder Leave only one static, no-arg build() method for access to a Builder. URI-related preferences are now exposed on the builder itself. Improve Javadoc with base URI examples. --- .../function/client/DefaultWebClient.java | 6 + .../client/DefaultWebClientBuilder.java | 82 +++++---- .../reactive/function/client/WebClient.java | 157 +++++++++++------- .../client/DefaultWebClientTests.java | 2 +- 4 files changed, 157 insertions(+), 90 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 239d716c37..0a6d365659 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -22,6 +22,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.Map; import java.util.function.Function; import org.jetbrains.annotations.NotNull; @@ -143,6 +144,11 @@ class DefaultWebClient implements WebClient { return uri(getUriBuilderFactory().expand(uriTemplate, uriVariables)); } + @Override + public HeaderSpec uri(String uriTemplate, Map uriVariables) { + return uri(getUriBuilderFactory().expand(uriTemplate, uriVariables)); + } + @Override public HeaderSpec uri(Function uriFunction) { return uri(uriFunction.apply(getUriBuilderFactory())); 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 609830a553..24e295aa84 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 @@ -17,6 +17,7 @@ package org.springframework.web.reactive.function.client; import java.util.Arrays; +import java.util.Map; import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; @@ -35,49 +36,38 @@ import org.springframework.web.util.UriBuilderFactory; */ class DefaultWebClientBuilder implements WebClient.Builder { + private String baseUrl; + + private Map defaultUriVariables; + private UriBuilderFactory uriBuilderFactory; + private HttpHeaders defaultHeaders; + + private MultiValueMap defaultCookies; + private ClientHttpConnector connector; private ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults(); private ExchangeFunction exchangeFunction; - private HttpHeaders defaultHeaders; - private MultiValueMap defaultCookies; - - - public DefaultWebClientBuilder() { - this(new DefaultUriBuilderFactory()); + @Override + public WebClient.Builder baseUrl(String baseUrl) { + this.baseUrl = baseUrl; + return this; } - public DefaultWebClientBuilder(String baseUrl) { - this(new DefaultUriBuilderFactory(baseUrl)); + @Override + public WebClient.Builder defaultUriVariables(Map defaultUriVariables) { + this.defaultUriVariables = defaultUriVariables; + return this; } - public DefaultWebClientBuilder(UriBuilderFactory uriBuilderFactory) { - Assert.notNull(uriBuilderFactory, "UriBuilderFactory is required."); + @Override + public WebClient.Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory) { this.uriBuilderFactory = uriBuilderFactory; - } - - - @Override - public WebClient.Builder clientConnector(ClientHttpConnector connector) { - this.connector = connector; - return this; - } - - @Override - public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) { - Assert.notNull(strategies, "ExchangeStrategies is required."); - this.exchangeStrategies = strategies; - return this; - } - - @Override - public WebClient.Builder exchangeFunction(ExchangeFunction exchangeFunction) { - this.exchangeFunction = exchangeFunction; return this; } @@ -101,10 +91,40 @@ class DefaultWebClientBuilder implements WebClient.Builder { return this; } + @Override + public WebClient.Builder clientConnector(ClientHttpConnector connector) { + this.connector = connector; + return this; + } + + @Override + public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) { + Assert.notNull(strategies, "ExchangeStrategies is required."); + this.exchangeStrategies = strategies; + return this; + } + + @Override + public WebClient.Builder exchangeFunction(ExchangeFunction exchangeFunction) { + this.exchangeFunction = exchangeFunction; + return this; + } + @Override public WebClient build() { - return new DefaultWebClient(initExchangeFunction(), - this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies); + return new DefaultWebClient(initExchangeFunction(), initUriBuilderFactory(), + this.defaultHeaders, this.defaultCookies); + } + + private UriBuilderFactory initUriBuilderFactory() { + if (this.uriBuilderFactory != null) { + return this.uriBuilderFactory; + } + DefaultUriBuilderFactory factory = this.baseUrl != null ? + new DefaultUriBuilderFactory(this.baseUrl) : new DefaultUriBuilderFactory(); + + factory.setDefaultUriVariables(this.defaultUriVariables); + return factory; } private ExchangeFunction initExchangeFunction() { 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 96bd2ee387..134c905462 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 @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; import java.nio.charset.Charset; import java.time.ZonedDateTime; +import java.util.Map; import java.util.function.Function; import org.reactivestreams.Publisher; @@ -30,7 +31,6 @@ import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; -import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.UriBuilderFactory; /** @@ -38,20 +38,13 @@ import org.springframework.web.util.UriBuilderFactory; * *
  *
- * // Create ExchangeFunction (application-wide)
+ * // Initialize the client
  *
- * ClientHttpConnector connector = new ReactorClientHttpConnector();
- * ExchangeFunction exchangeFunction = ExchangeFunctions.create(connector);
- *
- * // Create WebClient (per base URI)
- *
- * String baseUri = "http://abc.com";
- * UriBuilderFactory factory = new DefaultUriBuilderFactory(baseUri);
- * WebClient operations = WebClient.create(exchangeFunction, factory);
+ * WebClient client = WebClient.create("http://abc.com");
  *
  * // Perform requests...
  *
- * Mono result = operations.get()
+ * Mono<String> result = client.get()
  *     .uri("/foo")
  *     .exchange()
  *     .then(response -> response.bodyToMono(String.class));
@@ -118,24 +111,49 @@ public interface WebClient {
 	// Static, factory methods
 
 	/**
-	 * Shortcut for:
-	 * 
-	 * WebClient client = builder().build();
-	 * 
+ * Create a new {@code WebClient} with no default, shared preferences across + * requests such as base URI, default headers, and others. */ static WebClient create() { return new DefaultWebClientBuilder().build(); } /** - * Shortcut for: + * Configure a base URI for requests performed through the client for + * example to avoid repeating the same host, port, base path, or even + * query parameters with every request. + * + *

Given the following initialization: *

-	 * WebClient client = builder(baseUrl).build();
+	 * WebClient client = WebClient.create("http://abc.com/v1");
 	 * 
+ * + *

A base URI is applied when using a URI template: + *

+	 *
+	 * // GET http://abc.com/v1/accounts/43
+	 *
+	 * Mono<Account> result = client.get()
+	 *         .uri("/accounts/{id}", 43)
+	 *         .exchange()
+	 *         .then(response -> response.bodyToMono(String.class));
+	 * 
+ * + *

It is also applied when using a {@link UriBuilderFactory}: + *

+
+	 * // GET http://abc.com/v1/accounts?q=12
+	 *
+	 * Mono<Account> result = client.get()
+	 *         .uri(factory -> factory.uriString("/accounts").queryParam("q", "12").build())
+	 *         .exchange()
+	 *         .then(response -> response.bodyToMono(String.class));
+	 * 
+ * * @param baseUrl the base URI for all requests */ static WebClient create(String baseUrl) { - return new DefaultWebClientBuilder(baseUrl).build(); + return new DefaultWebClientBuilder().baseUrl(baseUrl).build(); } /** @@ -145,27 +163,6 @@ public interface WebClient { return new DefaultWebClientBuilder(); } - /** - * Obtain a {@code WebClient} builder with a base URI to be used as the - * base for expanding URI templates during exchanges. The given String - * is used to create an instance of {@link DefaultUriBuilderFactory} whose - * {@link DefaultUriBuilderFactory#DefaultUriBuilderFactory(String) - * constructor} provides more details on how the base URI is applied. - * @param baseUrl the base URI for all requests - */ - static WebClient.Builder builder(String baseUrl) { - return new DefaultWebClientBuilder(baseUrl); - } - - /** - * Obtain a {@code WebClient} builder with the {@link UriBuilderFactory} - * to use for expanding URI templates during exchanges. - * @param uriBuilderFactory the factory to use - */ - static WebClient.Builder builder(UriBuilderFactory uriBuilderFactory) { - return new DefaultWebClientBuilder(uriBuilderFactory); - } - /** * A mutable builder for a {@link WebClient}. @@ -173,30 +170,34 @@ public interface WebClient { interface Builder { /** - * Configure the {@link ClientHttpConnector} to use. - *

By default an instance of - * {@link org.springframework.http.client.reactive.ReactorClientHttpConnector - * ReactorClientHttpConnector} is created if this is not set. However a - * shared instance may be passed instead, e.g. for use with multiple - * {@code WebClient}'s targeting different base URIs. - * @param connector the connector to use + * Configure a base URI as described in {@link WebClient#create(String) + * WebClient.create(String)}. + * @see #defaultUriVariables(Map) + * @see #uriBuilderFactory(UriBuilderFactory) */ - Builder clientConnector(ClientHttpConnector connector); + Builder baseUrl(String baseUrl); /** - * Configure the {@link ExchangeStrategies} to use. - *

By default {@link ExchangeStrategies#withDefaults()} is used. - * @param strategies the strategies to use + * Configure default URI variable values that will be used when expanding + * URI templates using a {@link Map}. + * @param defaultUriVariables the default values to use + * @see #baseUrl(String) + * @see #uriBuilderFactory(UriBuilderFactory) */ - Builder exchangeStrategies(ExchangeStrategies strategies); + Builder defaultUriVariables(Map defaultUriVariables); /** - * Configure directly an {@link ExchangeFunction} instead of separately - * providing a {@link ClientHttpConnector} and/or - * {@link ExchangeStrategies}. - * @param exchangeFunction the exchange function to use + * Provide a pre-configured {@link UriBuilderFactory} instance. This is + * an alternative to and effectively overrides the following: + *

    + *
  • {@link #baseUrl(String)} + *
  • {@link #defaultUriVariables(Map)}. + *
+ * @param uriBuilderFactory the URI builder factory to use + * @see #baseUrl(String) + * @see #defaultUriVariables(Map) */ - Builder exchangeFunction(ExchangeFunction exchangeFunction); + Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); /** * Add the given header to all requests that haven't added it. @@ -212,6 +213,41 @@ public interface WebClient { */ Builder defaultCookie(String cookieName, String... cookieValues); + /** + * Configure the {@link ClientHttpConnector} to use. + *

By default an instance of + * {@link org.springframework.http.client.reactive.ReactorClientHttpConnector + * ReactorClientHttpConnector} is created if this is not set. However a + * shared instance may be passed instead, e.g. for use with multiple + * {@code WebClient}'s targeting different base URIs. + * @param connector the connector to use + * @see #exchangeStrategies(ExchangeStrategies) + * @see #exchangeFunction(ExchangeFunction) + */ + Builder clientConnector(ClientHttpConnector connector); + + /** + * Configure the {@link ExchangeStrategies} to use. + *

By default {@link ExchangeStrategies#withDefaults()} is used. + * @param strategies the strategies to use + * @see #clientConnector(ClientHttpConnector) + * @see #exchangeFunction(ExchangeFunction) + */ + Builder exchangeStrategies(ExchangeStrategies strategies); + + /** + * Provide a pre-configured {@link ExchangeFunction} instance. This is + * an alternative to and effectively overrides the following: + *

    + *
  • {@link #clientConnector(ClientHttpConnector)} + *
  • {@link #exchangeStrategies(ExchangeStrategies)}. + *
+ * @param exchangeFunction the exchange function to use + * @see #clientConnector(ClientHttpConnector) + * @see #exchangeStrategies(ExchangeStrategies) + */ + Builder exchangeFunction(ExchangeFunction exchangeFunction); + /** * Builder the {@link WebClient} instance. */ @@ -234,14 +270,19 @@ public interface WebClient { * Specify the URI for the request using a URI template and URI variables. * If a {@link UriBuilderFactory} was configured for the client (e.g. * with a base URI) it will be used to expand the URI template. - * @see #builder(String) */ HeaderSpec uri(String uri, Object... uriVariables); + /** + * Specify the URI for the request using a URI template and URI variables. + * If a {@link UriBuilderFactory} was configured for the client (e.g. + * with a base URI) it will be used to expand the URI template. + */ + HeaderSpec uri(String uri, Map uriVariables); + /** * Build the URI for the request using the {@link UriBuilderFactory} * configured for this client. - * @see #builder(String) */ HeaderSpec uri(Function uriFunction); 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 ad7e075acb..4e39d93b4d 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 @@ -99,7 +99,7 @@ public class DefaultWebClientTests { private WebClient.Builder builder() { - return WebClient.builder("/base").exchangeFunction(this.exchangeFunction); + return WebClient.builder().baseUrl("/base").exchangeFunction(this.exchangeFunction); } private ClientRequest verifyExchange() {