Rework HTTP client modules

This commit is contained in:
Andy Wilkinson
2025-05-13 12:49:28 +01:00
committed by Phillip Webb
parent 4763ef2463
commit 7a9be5bd4a
230 changed files with 371 additions and 1330 deletions

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import java.nio.charset.Charset;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
/**
* Basic authentication details to be applied to {@link HttpHeaders}.
*
* @author Dmytro Nosan
* @author Ilya Lukyanovich
*/
class BasicAuthentication {
private final String username;
private final String password;
private final Charset charset;
BasicAuthentication(String username, String password, Charset charset) {
Assert.notNull(username, "'username' must not be null");
Assert.notNull(password, "'password' must not be null");
this.username = username;
this.password = password;
this.charset = charset;
}
void applyTo(HttpHeaders headers) {
if (!headers.containsHeader(HttpHeaders.AUTHORIZATION)) {
headers.setBasicAuth(this.username, this.password, this.charset);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import org.springframework.web.client.RestClient;
/**
* Callback interface that can be used to customize a
* {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}.
*
* @author Arjen Poutsma
* @since 4.0.0
*/
@FunctionalInterface
public interface RestClientCustomizer {
/**
* Callback to customize a {@link org.springframework.web.client.RestClient.Builder
* RestClient.Builder} instance.
* @param restClientBuilder the client builder to customize
*/
void customize(RestClient.Builder restClientBuilder);
}

View File

@@ -0,0 +1,789 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
/**
* Builder that can be used to configure and create a {@link RestTemplate}. Provides
* convenience methods to register {@link #messageConverters(HttpMessageConverter...)
* converters}, {@link #errorHandler(ResponseErrorHandler) error handlers} and
* {@link #uriTemplateHandler(UriTemplateHandler) UriTemplateHandlers}.
* <p>
* By default, the built {@link RestTemplate} will attempt to use the most suitable
* {@link ClientHttpRequestFactory}, call {@link #detectRequestFactory(boolean)
* detectRequestFactory(false)} if you prefer to keep the default. In a typical
* auto-configured Spring Boot application this builder is available as a bean and can be
* injected whenever a {@link RestTemplate} is needed.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Brian Clozel
* @author Dmytro Nosan
* @author Kevin Strijbos
* @author Ilya Lukyanovich
* @author Scott Frederick
* @author Yanming Zhou
* @since 4.0.0
*/
public class RestTemplateBuilder {
private final ClientHttpRequestFactorySettings requestFactorySettings;
private final boolean detectRequestFactory;
private final String rootUri;
private final Set<HttpMessageConverter<?>> messageConverters;
private final Set<ClientHttpRequestInterceptor> interceptors;
private final ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
private final UriTemplateHandler uriTemplateHandler;
private final ResponseErrorHandler errorHandler;
private final BasicAuthentication basicAuthentication;
private final Map<String, List<String>> defaultHeaders;
private final Set<RestTemplateCustomizer> customizers;
private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
/**
* Create a new {@link RestTemplateBuilder} instance.
* @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that
* should be applied when the {@link RestTemplate} is built
*/
public RestTemplateBuilder(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
this.requestFactorySettings = ClientHttpRequestFactorySettings.defaults();
this.detectRequestFactory = true;
this.rootUri = null;
this.messageConverters = null;
this.interceptors = Collections.emptySet();
this.requestFactoryBuilder = null;
this.uriTemplateHandler = null;
this.errorHandler = null;
this.basicAuthentication = null;
this.defaultHeaders = Collections.emptyMap();
this.customizers = copiedSetOf(customizers);
this.requestCustomizers = Collections.emptySet();
}
private RestTemplateBuilder(ClientHttpRequestFactorySettings requestFactorySettings, boolean detectRequestFactory,
String rootUri, Set<HttpMessageConverter<?>> messageConverters,
Set<ClientHttpRequestInterceptor> interceptors, ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder,
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
BasicAuthentication basicAuthentication, Map<String, List<String>> defaultHeaders,
Set<RestTemplateCustomizer> customizers, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.requestFactorySettings = requestFactorySettings;
this.detectRequestFactory = detectRequestFactory;
this.rootUri = rootUri;
this.messageConverters = messageConverters;
this.interceptors = interceptors;
this.requestFactoryBuilder = requestFactoryBuilder;
this.uriTemplateHandler = uriTemplateHandler;
this.errorHandler = errorHandler;
this.basicAuthentication = basicAuthentication;
this.defaultHeaders = defaultHeaders;
this.customizers = customizers;
this.requestCustomizers = requestCustomizers;
}
/**
* Set if the {@link ClientHttpRequestFactory} should be detected based on the
* classpath. Default if {@code true}.
* @param detectRequestFactory if the {@link ClientHttpRequestFactory} should be
* detected
* @return a new builder instance
*/
public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) {
return new RestTemplateBuilder(this.requestFactorySettings, detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Set a root URL that should be applied to each request that starts with {@code '/'}.
* The root URL will only apply when {@code String} variants of the
* {@link RestTemplate} methods are used for specifying the request URL.
* @param rootUri the root URI or {@code null}
* @return a new builder instance
*/
public RestTemplateBuilder rootUri(String rootUri) {
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate}. Setting this value will replace any previously configured
* converters and any converters configured on the builder will replace RestTemplate's
* default converters.
* @param messageConverters the converters to set
* @return a new builder instance
* @see #additionalMessageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder messageConverters(HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "'messageConverters' must not be null");
return messageConverters(Arrays.asList(messageConverters));
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate}. Setting this value will replace any previously configured
* converters and any converters configured on the builder will replace RestTemplate's
* default converters.
* @param messageConverters the converters to set
* @return a new builder instance
* @see #additionalMessageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder messageConverters(Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "'messageConverters' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
copiedSetOf(messageConverters), this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
* used with the {@link RestTemplate}. Any converters configured on the builder will
* replace RestTemplate's default converters.
* @param messageConverters the converters to add
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder additionalMessageConverters(HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "'messageConverters' must not be null");
return additionalMessageConverters(Arrays.asList(messageConverters));
}
/**
* Add additional {@link HttpMessageConverter HttpMessageConverters} that should be
* used with the {@link RestTemplate}. Any converters configured on the builder will
* replace RestTemplate's default converters.
* @param messageConverters the converters to add
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder additionalMessageConverters(
Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "'messageConverters' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
append(this.messageConverters, messageConverters), this.interceptors, this.requestFactoryBuilder,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
* the {@link RestTemplate} to the default set. Calling this method will replace any
* previously defined converters.
* @return a new builder instance
* @see #messageConverters(HttpMessageConverter...)
*/
public RestTemplateBuilder defaultMessageConverters() {
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
copiedSetOf(new RestTemplate().getMessageConverters()), this.interceptors, this.requestFactoryBuilder,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that
* should be used with the {@link RestTemplate}. Setting this value will replace any
* previously defined interceptors.
* @param interceptors the interceptors to set
* @return a new builder instance
* @since 1.4.1
* @see #additionalInterceptors(ClientHttpRequestInterceptor...)
*/
public RestTemplateBuilder interceptors(ClientHttpRequestInterceptor... interceptors) {
Assert.notNull(interceptors, "'interceptors' must not be null");
return interceptors(Arrays.asList(interceptors));
}
/**
* Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that
* should be used with the {@link RestTemplate}. Setting this value will replace any
* previously defined interceptors.
* @param interceptors the interceptors to set
* @return a new builder instance
* @since 1.4.1
* @see #additionalInterceptors(ClientHttpRequestInterceptor...)
*/
public RestTemplateBuilder interceptors(Collection<ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "'interceptors' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, copiedSetOf(interceptors), this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors}
* that should be used with the {@link RestTemplate}.
* @param interceptors the interceptors to add
* @return a new builder instance
* @since 1.4.1
* @see #interceptors(ClientHttpRequestInterceptor...)
*/
public RestTemplateBuilder additionalInterceptors(ClientHttpRequestInterceptor... interceptors) {
Assert.notNull(interceptors, "'interceptors' must not be null");
return additionalInterceptors(Arrays.asList(interceptors));
}
/**
* Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors}
* that should be used with the {@link RestTemplate}.
* @param interceptors the interceptors to add
* @return a new builder instance
* @since 1.4.1
* @see #interceptors(ClientHttpRequestInterceptor...)
*/
public RestTemplateBuilder additionalInterceptors(Collection<? extends ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "'interceptors' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, append(this.interceptors, interceptors), this.requestFactoryBuilder,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Set the {@link ClientHttpRequestFactory} class that should be used with the
* {@link RestTemplate}.
* @param requestFactoryType the request factory type to use
* @return a new builder instance
* @see ClientHttpRequestFactoryBuilder#of(Class)
* @see #requestFactoryBuilder(ClientHttpRequestFactoryBuilder)
*/
public RestTemplateBuilder requestFactory(Class<? extends ClientHttpRequestFactory> requestFactoryType) {
Assert.notNull(requestFactoryType, "'requestFactoryType' must not be null");
return requestFactoryBuilder(ClientHttpRequestFactoryBuilder.of(requestFactoryType));
}
/**
* Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called
* each time we {@link #build()} a new {@link RestTemplate} instance.
* @param requestFactorySupplier the supplier for the request factory
* @return a new builder instance
* @since 2.0.0
* @see ClientHttpRequestFactoryBuilder#of(Supplier)
* @see #requestFactoryBuilder(ClientHttpRequestFactoryBuilder)
*/
public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) {
Assert.notNull(requestFactorySupplier, "'requestFactorySupplier' must not be null");
return requestFactoryBuilder(ClientHttpRequestFactoryBuilder.of(requestFactorySupplier));
}
/**
* Set the {@link ClientHttpRequestFactoryBuilder} that should be used each time we
* {@link #build()} a new {@link RestTemplate} instance.
* @param requestFactoryBuilder the {@link ClientHttpRequestFactoryBuilder} to use
* @return a new builder instance
* @since 3.4.0
* @see ClientHttpRequestFactoryBuilder
*/
public RestTemplateBuilder requestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
Assert.notNull(requestFactoryBuilder, "'requestFactoryBuilder' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Set the {@link UriTemplateHandler} that should be used with the
* {@link RestTemplate}.
* @param uriTemplateHandler the URI template handler to use
* @return a new builder instance
*/
public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) {
Assert.notNull(uriTemplateHandler, "'uriTemplateHandler' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Set the {@link ResponseErrorHandler} that should be used with the
* {@link RestTemplate}.
* @param errorHandler the error handler to use
* @return a new builder instance
*/
public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "'errorHandler' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
}
/**
* Add HTTP Basic Authentication to requests with the given username/password pair,
* unless a custom Authorization header has been set before.
* @param username the user name
* @param password the password
* @return a new builder instance
* @since 2.1.0
* @see #basicAuthentication(String, String, Charset)
*/
public RestTemplateBuilder basicAuthentication(String username, String password) {
return basicAuthentication(username, password, null);
}
/**
* Add HTTP Basic Authentication to requests with the given username/password pair,
* unless a custom Authorization header has been set before.
* @param username the user name
* @param password the password
* @param charset the charset to use
* @return a new builder instance
* @since 2.2.0
*/
public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) {
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, new BasicAuthentication(username, password, charset), this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Add a default header that will be set if not already present on the outgoing
* {@link ClientHttpRequest}.
* @param name the name of the header
* @param values the header values
* @return a new builder instance
* @since 2.2.0
*/
public RestTemplateBuilder defaultHeader(String name, String... values) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(values, "'values' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, append(this.defaultHeaders, name, values),
this.customizers, this.requestCustomizers);
}
/**
* Sets the {@link ClientHttpRequestFactorySettings}. This will replace any previously
* set {@link #connectTimeout(Duration) connectTimeout}, {@link #readTimeout(Duration)
* readTimeout} and {@link #sslBundle(SslBundle) sslBundle} values.
* @param requestFactorySettings the request factory settings
* @return a new builder instance
* @since 3.4.0
*/
public RestTemplateBuilder requestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
Assert.notNull(requestFactorySettings, "'requestFactorySettings' must not be null");
return new RestTemplateBuilder(requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
}
/**
* Update the {@link ClientHttpRequestFactorySettings} using the given customizer.
* @param requestFactorySettingsCustomizer a {@link UnaryOperator} to update request
* factory settings
* @return a new builder instance
* @since 3.4.1
*/
public RestTemplateBuilder requestFactorySettings(
UnaryOperator<ClientHttpRequestFactorySettings> requestFactorySettingsCustomizer) {
Assert.notNull(requestFactorySettingsCustomizer, "'requestFactorySettingsCustomizer' must not be null");
return new RestTemplateBuilder(requestFactorySettingsCustomizer.apply(this.requestFactorySettings),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
this.defaultHeaders, this.customizers, this.requestCustomizers);
}
/**
* Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}.
* @param connectTimeout the connection timeout
* @return a new builder instance.
* @since 2.1.0
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
* {@link #connectTimeout(Duration)}
*/
@Deprecated(since = "3.4.0", forRemoval = true)
public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) {
return connectTimeout(connectTimeout);
}
/**
* Sets the connection timeout on the underlying {@link ClientHttpRequestFactory}.
* @param connectTimeout the connection timeout
* @return a new builder instance.
* @since 3.4.0
*/
public RestTemplateBuilder connectTimeout(Duration connectTimeout) {
return new RestTemplateBuilder(this.requestFactorySettings.withConnectTimeout(connectTimeout),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
this.defaultHeaders, this.customizers, this.requestCustomizers);
}
/**
* Sets the read timeout on the underlying {@link ClientHttpRequestFactory}.
* @param readTimeout the read timeout
* @return a new builder instance.
* @since 2.1.0
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
* {@link #readTimeout(Duration)}
*/
@Deprecated(since = "3.4.0", forRemoval = true)
public RestTemplateBuilder setReadTimeout(Duration readTimeout) {
return readTimeout(readTimeout);
}
/**
* Sets the read timeout on the underlying {@link ClientHttpRequestFactory}.
* @param readTimeout the read timeout
* @return a new builder instance.
* @since 3.4.0
*/
public RestTemplateBuilder readTimeout(Duration readTimeout) {
return new RestTemplateBuilder(this.requestFactorySettings.withReadTimeout(readTimeout),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors,
this.requestFactoryBuilder, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication,
this.defaultHeaders, this.customizers, this.requestCustomizers);
}
/**
* Sets the redirect strategy on the underlying {@link ClientHttpRequestFactory}.
* @param redirects the redirect strategy
* @return a new builder instance.
* @since 4.0.0
*/
public RestTemplateBuilder redirects(HttpRedirects redirects) {
return new RestTemplateBuilder(this.requestFactorySettings.withRedirects(redirects), this.detectRequestFactory,
this.rootUri, this.messageConverters, this.interceptors, this.requestFactoryBuilder,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Sets the SSL bundle on the underlying {@link ClientHttpRequestFactory}.
* @param sslBundle the SSL bundle
* @return a new builder instance
* @since 3.1.0
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
* {@link #sslBundle(SslBundle)}
*/
@Deprecated(since = "3.4.0", forRemoval = true)
public RestTemplateBuilder setSslBundle(SslBundle sslBundle) {
return sslBundle(sslBundle);
}
/**
* Sets the SSL bundle on the underlying {@link ClientHttpRequestFactory}.
* @param sslBundle the SSL bundle
* @return a new builder instance
* @since 3.4.0
*/
public RestTemplateBuilder sslBundle(SslBundle sslBundle) {
return new RestTemplateBuilder(this.requestFactorySettings.withSslBundle(sslBundle), this.detectRequestFactory,
this.rootUri, this.messageConverters, this.interceptors, this.requestFactoryBuilder,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers);
}
/**
* Set the {@link RestTemplateCustomizer RestTemplateCustomizers} that should be
* applied to the {@link RestTemplate}. Customizers are applied in the order that they
* were added after builder configuration has been applied. Setting this value will
* replace any previously configured customizers.
* @param customizers the customizers to set
* @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder customizers(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
return customizers(Arrays.asList(customizers));
}
/**
* Set the {@link RestTemplateCustomizer RestTemplateCustomizers} that should be
* applied to the {@link RestTemplate}. Customizers are applied in the order that they
* were added after builder configuration has been applied. Setting this value will
* replace any previously configured customizers.
* @param customizers the customizers to set
* @return a new builder instance
* @see #additionalCustomizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, copiedSetOf(customizers),
this.requestCustomizers);
}
/**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied
* to the {@link RestTemplate}. Customizers are applied in the order that they were
* added after builder configuration has been applied.
* @param customizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
return additionalCustomizers(Arrays.asList(customizers));
}
/**
* Add {@link RestTemplateCustomizer RestTemplateCustomizers} that should be applied
* to the {@link RestTemplate}. Customizers are applied in the order that they were
* added after builder configuration has been applied.
* @param customizers the customizers to add
* @return a new builder instance
* @see #customizers(RestTemplateCustomizer...)
*/
public RestTemplateBuilder additionalCustomizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "'customizers' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, append(this.customizers, customizers),
this.requestCustomizers);
}
/**
* Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* order that they were added. Setting this value will replace any previously
* configured request customizers.
* @param requestCustomizers the request customizers to set
* @return a new builder instance
* @since 2.2.0
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
*/
public RestTemplateBuilder requestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
return requestCustomizers(Arrays.asList(requestCustomizers));
}
/**
* Set the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* order that they were added. Setting this value will replace any previously
* configured request customizers.
* @param requestCustomizers the request customizers to set
* @return a new builder instance
* @since 2.2.0
* @see #additionalRequestCustomizers(RestTemplateRequestCustomizer...)
*/
public RestTemplateBuilder requestCustomizers(
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
copiedSetOf(requestCustomizers));
}
/**
* Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* order that they were added.
* @param requestCustomizers the request customizers to add
* @return a new builder instance
* @since 2.2.0
* @see #requestCustomizers(RestTemplateRequestCustomizer...)
*/
public RestTemplateBuilder additionalRequestCustomizers(RestTemplateRequestCustomizer<?>... requestCustomizers) {
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
return additionalRequestCustomizers(Arrays.asList(requestCustomizers));
}
/**
* Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
* should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
* order that they were added.
* @param requestCustomizers the request customizers to add
* @return a new builder instance
* @since 2.2.0
* @see #requestCustomizers(Collection)
*/
public RestTemplateBuilder additionalRequestCustomizers(
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
Assert.notNull(requestCustomizers, "'requestCustomizers' must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactoryBuilder, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
append(this.requestCustomizers, requestCustomizers));
}
/**
* Build a new {@link RestTemplate} instance and configure it using this builder.
* @return a configured {@link RestTemplate} instance.
* @see #build(Class)
* @see #configure(RestTemplate)
*/
public RestTemplate build() {
return configure(new RestTemplate());
}
/**
* Build a new {@link RestTemplate} instance of the specified type and configure it
* using this builder.
* @param <T> the type of rest template
* @param restTemplateClass the template type to create
* @return a configured {@link RestTemplate} instance.
* @see RestTemplateBuilder#build()
* @see #configure(RestTemplate)
*/
public <T extends RestTemplate> T build(Class<T> restTemplateClass) {
return configure(BeanUtils.instantiateClass(restTemplateClass));
}
/**
* Configure the provided {@link RestTemplate} instance using this builder.
* @param <T> the type of rest template
* @param restTemplate the {@link RestTemplate} to configure
* @return the rest template instance
* @see RestTemplateBuilder#build()
* @see RestTemplateBuilder#build(Class)
*/
public <T extends RestTemplate> T configure(T restTemplate) {
ClientHttpRequestFactory requestFactory = buildRequestFactory();
if (requestFactory != null) {
restTemplate.setRequestFactory(requestFactory);
}
addClientHttpRequestInitializer(restTemplate);
if (!CollectionUtils.isEmpty(this.messageConverters)) {
restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters));
}
if (this.uriTemplateHandler != null) {
restTemplate.setUriTemplateHandler(this.uriTemplateHandler);
}
if (this.errorHandler != null) {
restTemplate.setErrorHandler(this.errorHandler);
}
if (this.rootUri != null) {
RootUriBuilderFactory.applyTo(restTemplate, this.rootUri);
}
restTemplate.getInterceptors().addAll(this.interceptors);
if (!CollectionUtils.isEmpty(this.customizers)) {
for (RestTemplateCustomizer customizer : this.customizers) {
customizer.customize(restTemplate);
}
}
return restTemplate;
}
/**
* Build a new {@link ClientHttpRequestFactory} instance using the settings of this
* builder.
* @return a {@link ClientHttpRequestFactory} or {@code null}
* @since 2.2.0
*/
public ClientHttpRequestFactory buildRequestFactory() {
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder = requestFactoryBuilder();
return (requestFactoryBuilder != null) ? requestFactoryBuilder.build(this.requestFactorySettings) : null;
}
/**
* Return a {@link ClientHttpRequestFactoryBuilder} instance using the settings of
* this builder.
* @return a {@link ClientHttpRequestFactoryBuilder} or {@code null}
* @since 3.5.0
*/
public ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder() {
if (this.requestFactoryBuilder != null) {
return this.requestFactoryBuilder;
}
if (this.detectRequestFactory) {
return ClientHttpRequestFactoryBuilder.detect();
}
return null;
}
private void addClientHttpRequestInitializer(RestTemplate restTemplate) {
if (this.basicAuthentication == null && this.defaultHeaders.isEmpty() && this.requestCustomizers.isEmpty()) {
return;
}
restTemplate.getClientHttpRequestInitializers()
.add(new RestTemplateBuilderClientHttpRequestInitializer(this.basicAuthentication, this.defaultHeaders,
this.requestCustomizers));
}
@SuppressWarnings("unchecked")
private <T> Set<T> copiedSetOf(T... items) {
return copiedSetOf(Arrays.asList(items));
}
private <T> Set<T> copiedSetOf(Collection<? extends T> collection) {
return Collections.unmodifiableSet(new LinkedHashSet<>(collection));
}
private static <T> List<T> copiedListOf(T[] items) {
return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(items, items.length)));
}
private static <T> Set<T> append(Collection<? extends T> collection, Collection<? extends T> additions) {
Set<T> result = new LinkedHashSet<>((collection != null) ? collection : Collections.emptySet());
if (additions != null) {
result.addAll(additions);
}
return Collections.unmodifiableSet(result);
}
private static <K, V> Map<K, List<V>> append(Map<K, List<V>> map, K key, V[] values) {
Map<K, List<V>> result = new LinkedHashMap<>((map != null) ? map : Collections.emptyMap());
if (values != null) {
result.put(key, copiedListOf(values));
}
return Collections.unmodifiableMap(result);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestInitializer;
/**
* {@link ClientHttpRequestInitializer} to apply customizations from the
* {@link RestTemplateBuilder}.
*
* @author Dmytro Nosan
* @author Ilya Lukyanovich
*/
class RestTemplateBuilderClientHttpRequestInitializer implements ClientHttpRequestInitializer {
private final BasicAuthentication basicAuthentication;
private final Map<String, List<String>> defaultHeaders;
private final Set<RestTemplateRequestCustomizer<?>> requestCustomizers;
RestTemplateBuilderClientHttpRequestInitializer(BasicAuthentication basicAuthentication,
Map<String, List<String>> defaultHeaders, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.basicAuthentication = basicAuthentication;
this.defaultHeaders = defaultHeaders;
this.requestCustomizers = requestCustomizers;
}
@Override
@SuppressWarnings("unchecked")
public void initialize(ClientHttpRequest request) {
HttpHeaders headers = request.getHeaders();
if (this.basicAuthentication != null) {
this.basicAuthentication.applyTo(headers);
}
this.defaultHeaders.forEach(headers::putIfAbsent);
LambdaSafe.callbacks(RestTemplateRequestCustomizer.class, this.requestCustomizers, request)
.invoke((customizer) -> customizer.customize(request));
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import org.springframework.web.client.RestTemplate;
/**
* Callback interface that can be used to customize a {@link RestTemplate}.
*
* @author Phillip Webb
* @since 4.0.0
* @see RestTemplateBuilder
*/
@FunctionalInterface
public interface RestTemplateCustomizer {
/**
* Callback to customize a {@link RestTemplate} instance.
* @param restTemplate the template to customize
*/
void customize(RestTemplate restTemplate);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestInitializer;
import org.springframework.web.client.RestTemplate;
/**
* Callback interface that can be used to customize the {@link ClientHttpRequest} sent
* from a {@link RestTemplate}.
*
* @param <T> the {@link ClientHttpRequest} type
* @author Ilya Lukyanovich
* @author Phillip Webb
* @since 4.0.0
* @see RestTemplateBuilder
* @see ClientHttpRequestInitializer
*/
@FunctionalInterface
public interface RestTemplateRequestCustomizer<T extends ClientHttpRequest> {
/**
* Customize the specified {@link ClientHttpRequest}.
* @param request the request to customize
*/
void customize(T request);
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriBuilderFactory} to set the root for URI that starts with {@code '/'}.
*
* @author Scott Frederick
* @since 4.0.0
*/
public class RootUriBuilderFactory extends RootUriTemplateHandler implements UriBuilderFactory {
RootUriBuilderFactory(String rootUri, UriTemplateHandler delegate) {
super(rootUri, delegate);
}
@Override
public UriBuilder uriString(String uriTemplate) {
return UriComponentsBuilder.fromUriString(apply(uriTemplate));
}
@Override
public UriBuilder builder() {
return UriComponentsBuilder.newInstance();
}
/**
* Apply a {@link RootUriBuilderFactory} instance to the given {@link RestTemplate}.
* @param restTemplate the {@link RestTemplate} to add the builder factory to
* @param rootUri the root URI
*/
static void applyTo(RestTemplate restTemplate, String rootUri) {
Assert.notNull(restTemplate, "'restTemplate' must not be null");
RootUriBuilderFactory handler = new RootUriBuilderFactory(rootUri, restTemplate.getUriTemplateHandler());
restTemplate.setUriTemplateHandler(handler);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient;
import java.net.URI;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriTemplateHandler} to set the root for URI that starts with {@code '/'}.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 4.0.0
*/
public class RootUriTemplateHandler implements UriTemplateHandler {
private final String rootUri;
private final UriTemplateHandler handler;
protected RootUriTemplateHandler(UriTemplateHandler handler) {
Assert.notNull(handler, "'handler' must not be null");
this.rootUri = null;
this.handler = handler;
}
RootUriTemplateHandler(String rootUri, UriTemplateHandler handler) {
Assert.notNull(rootUri, "'rootUri' must not be null");
Assert.notNull(handler, "'handler' must not be null");
this.rootUri = rootUri;
this.handler = handler;
}
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return this.handler.expand(apply(uriTemplate), uriVariables);
}
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return this.handler.expand(apply(uriTemplate), uriVariables);
}
String apply(String uriTemplate) {
if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) {
return getRootUri() + uriTemplate;
}
return uriTemplate;
}
public String getRootUri() {
return this.rootUri;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.actuate.observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClient.Builder;
/**
* {@link RestClientCustomizer} that configures the {@link Builder RestClient builder} to
* record request observations.
*
* @author Moritz Halbritter
* @since 4.0.0
*/
public class ObservationRestClientCustomizer implements RestClientCustomizer {
private final ObservationRegistry observationRegistry;
private final ClientRequestObservationConvention observationConvention;
/**
* Create a new {@link ObservationRestClientCustomizer}.
* @param observationRegistry the observation registry
* @param observationConvention the observation convention
*/
public ObservationRestClientCustomizer(ObservationRegistry observationRegistry,
ClientRequestObservationConvention observationConvention) {
Assert.notNull(observationConvention, "'observationConvention' must not be null");
Assert.notNull(observationRegistry, "'observationRegistry' must not be null");
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
}
@Override
public void customize(Builder restClientBuilder) {
restClientBuilder.observationRegistry(this.observationRegistry);
restClientBuilder.observationConvention(this.observationConvention);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.actuate.observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.restclient.RestTemplateCustomizer;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.web.client.RestTemplate;
/**
* {@link RestTemplateCustomizer} that configures the {@link RestTemplate} to record
* request observations.
*
* @author Brian Clozel
* @since 4.0.0
*/
public class ObservationRestTemplateCustomizer implements RestTemplateCustomizer {
private final ObservationRegistry observationRegistry;
private final ClientRequestObservationConvention observationConvention;
/**
* Create a new {@code ObservationRestTemplateCustomizer}.
* @param observationConvention the observation convention
* @param observationRegistry the observation registry
*/
public ObservationRestTemplateCustomizer(ObservationRegistry observationRegistry,
ClientRequestObservationConvention observationConvention) {
this.observationConvention = observationConvention;
this.observationRegistry = observationRegistry;
}
@Override
public void customize(RestTemplate restTemplate) {
restTemplate.setObservationConvention(this.observationConvention);
restTemplate.setObservationRegistry(this.observationRegistry);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Observation integration for RestClient and RestTemplate.
*/
package org.springframework.boot.restclient.actuate.observation;

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import java.util.function.Consumer;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
/**
* An auto-configured {@link RestClientSsl} implementation.
*
* @author Phillip Webb
* @author Dmytro Nosan
*/
class AutoConfiguredRestClientSsl implements RestClientSsl {
private final ClientHttpRequestFactoryBuilder<?> builder;
private final ClientHttpRequestFactorySettings settings;
private final SslBundles sslBundles;
AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder,
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings, SslBundles sslBundles) {
this.builder = clientHttpRequestFactoryBuilder;
this.settings = clientHttpRequestFactorySettings;
this.sslBundles = sslBundles;
}
@Override
public Consumer<RestClient.Builder> fromBundle(String bundleName) {
return fromBundle(this.sslBundles.getBundle(bundleName));
}
@Override
public Consumer<RestClient.Builder> fromBundle(SslBundle bundle) {
return (builder) -> builder.requestFactory(requestFactory(bundle));
}
private ClientHttpRequestFactory requestFactory(SslBundle bundle) {
return this.builder.build(this.settings.withSslBundle(bundle));
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClient;
/**
* {@link RestClientCustomizer} to apply {@link HttpMessageConverter
* HttpMessageConverters}.
*
* @author Phillip Webb
* @since 4.0.0
*/
public class HttpMessageConvertersRestClientCustomizer implements RestClientCustomizer {
private final Iterable<? extends HttpMessageConverter<?>> messageConverters;
public HttpMessageConvertersRestClientCustomizer(HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "'messageConverters' must not be null");
this.messageConverters = Arrays.asList(messageConverters);
}
HttpMessageConvertersRestClientCustomizer(HttpMessageConverters messageConverters) {
this.messageConverters = messageConverters;
}
@Override
public void customize(RestClient.Builder restClientBuilder) {
restClientBuilder.messageConverters(this::configureMessageConverters);
}
private void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
if (this.messageConverters != null) {
messageConverters.clear();
this.messageConverters.forEach(messageConverters::add);
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
/**
* {@link SpringBootCondition} that applies only when running in a non-reactive web
* application.
*
* @author Phillip Webb
*/
class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
private static final class ReactiveWebApplication {
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.context.annotation.Conditional;
/**
* {@link SpringBootCondition} that applies when running in a non-reactive web application
* or virtual threads are enabled.
*
* @author Dmitry Sulman
*/
class NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition extends AnyNestedCondition {
NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Conditional(NotReactiveWebApplicationCondition.class)
private static final class NotReactiveWebApplication {
}
@ConditionalOnThreading(Threading.VIRTUAL)
@ConditionalOnBean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
private static final class VirtualThreadsExecutorEnabled {
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Scope;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.Builder;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestClient}.
* <p>
* This will produce a {@link Builder RestClient.Builder} bean with the {@code prototype}
* scope, meaning each injection point will receive a newly cloned instance of the
* builder.
*
* @author Arjen Poutsma
* @author Moritz Halbritter
* @since 4.0.0
*/
@AutoConfiguration(
after = { HttpClientAutoConfiguration.class, TaskExecutionAutoConfiguration.class, SslAutoConfiguration.class })
@ConditionalOnClass(RestClient.class)
@Conditional(NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.class)
public class RestClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RestClientSsl.class)
@ConditionalOnBean(SslBundles.class)
AutoConfiguredRestClientSsl restClientSsl(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings, SslBundles sslBundles) {
return new AutoConfiguredRestClientSsl(
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults),
sslBundles);
}
@Bean
@ConditionalOnMissingBean
RestClientBuilderConfigurer restClientBuilderConfigurer(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
ObjectProvider<RestClientCustomizer> customizerProvider) {
return new RestClientBuilderConfigurer(
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults),
customizerProvider.orderedStream().toList());
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@ConditionalOnMissingBean
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
return restClientBuilderConfigurer.configure(RestClient.builder());
}
@ConditionalOnClass(HttpMessageConverters.class)
static class HttpMessageConvertersConfiguration {
@Bean
@ConditionalOnMissingBean
@Order(Ordered.LOWEST_PRECEDENCE)
HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomizer(
ObjectProvider<HttpMessageConverters> messageConverters) {
return new HttpMessageConvertersRestClientCustomizer(messageConverters.getIfUnique());
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.Builder;
/**
* Configure {@link Builder RestClient.Builder} with sensible defaults.
* <p>
* Can be injected into application code and used to define a custom
* {@code RestClient.Builder} whose configuration is based upon that produced by
* auto-configuration.
*
* @author Moritz Halbritter
* @since 4.0.0
*/
public class RestClientBuilderConfigurer {
private final ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
private final ClientHttpRequestFactorySettings requestFactorySettings;
private final List<RestClientCustomizer> customizers;
public RestClientBuilderConfigurer() {
this(ClientHttpRequestFactoryBuilder.detect(), ClientHttpRequestFactorySettings.defaults(),
Collections.emptyList());
}
RestClientBuilderConfigurer(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder,
ClientHttpRequestFactorySettings requestFactorySettings, List<RestClientCustomizer> customizers) {
this.requestFactoryBuilder = requestFactoryBuilder;
this.requestFactorySettings = requestFactorySettings;
this.customizers = customizers;
}
/**
* Configure the specified {@link Builder RestClient.Builder}. The builder can be
* further tuned and default settings can be overridden.
* @param builder the {@link Builder RestClient.Builder} instance to configure
* @return the configured builder
*/
public RestClient.Builder configure(RestClient.Builder builder) {
builder.requestFactory(this.requestFactoryBuilder.build(this.requestFactorySettings));
applyCustomizers(builder);
return builder;
}
private void applyCustomizers(Builder builder) {
for (RestClientCustomizer customizer : this.customizers) {
customizer.customize(builder);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import java.util.function.Consumer;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
/**
* Interface that can be used to {@link RestClient.Builder#apply apply} SSL configuration
* to a {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}.
* <p>
* Typically used as follows: <pre class="code">
* &#064;Bean
* public MyBean myBean(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
* RestClient restClient = restClientBuilder.apply(ssl.fromBundle("mybundle")).build();
* return new MyBean(restClient);
* }
* </pre> NOTE: Applying SSL configuration will replace any previously
* {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}.
* The replacement {@link ClientHttpRequestFactory} will apply only configured
* {@link ClientHttpRequestFactorySettings} and the appropriate {@link SslBundle}.
* <p>
* If you need to configure {@link ClientHttpRequestFactory} with more than just SSL
* consider using a {@link ClientHttpRequestFactoryBuilder}.
*
* @author Phillip Webb
* @since 4.0.0
*/
public interface RestClientSsl {
/**
* Return a {@link Consumer} that will apply SSL configuration for the named
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
* RestClient.Builder}.
* @param bundleName the name of the SSL bundle to apply
* @return a {@link Consumer} to apply the configuration
* @throws NoSuchSslBundleException if a bundle with the provided name does not exist
*/
Consumer<RestClient.Builder> fromBundle(String bundleName) throws NoSuchSslBundleException;
/**
* Return a {@link Consumer} that will apply SSL configuration for the
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
* RestClient.Builder}.
* @param bundle the SSL bundle to apply
* @return a {@link Consumer} to apply the configuration
*/
Consumer<RestClient.Builder> fromBundle(SslBundle bundle);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.RestTemplateCustomizer;
import org.springframework.boot.restclient.RestTemplateRequestCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.client.RestTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate} (via
* {@link RestTemplateBuilder}).
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 4.0.0
*/
@AutoConfiguration(after = HttpClientAutoConfiguration.class)
@ConditionalOnClass({ RestTemplate.class, HttpMessageConverters.class })
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
@Bean
@Lazy
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable());
configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable());
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().toList());
configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().toList());
return configurer;
}
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
return restTemplateBuilderConfigurer.configure(new RestTemplateBuilder());
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.autoconfigure;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.RestTemplateCustomizer;
import org.springframework.boot.restclient.RestTemplateRequestCustomizer;
import org.springframework.util.ObjectUtils;
/**
* Configure {@link RestTemplateBuilder} with sensible defaults.
* <p>
* Can be injected into application code and used to define a custom
* {@code RestTemplateBuilder} whose configuration is based upon that produced by
* auto-configuration.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
public final class RestTemplateBuilderConfigurer {
private ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
private ClientHttpRequestFactorySettings requestFactorySettings;
private HttpMessageConverters httpMessageConverters;
private List<RestTemplateCustomizer> restTemplateCustomizers;
private List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers;
void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
this.requestFactoryBuilder = requestFactoryBuilder;
}
void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
this.requestFactorySettings = requestFactorySettings;
}
void setHttpMessageConverters(HttpMessageConverters httpMessageConverters) {
this.httpMessageConverters = httpMessageConverters;
}
void setRestTemplateCustomizers(List<RestTemplateCustomizer> restTemplateCustomizers) {
this.restTemplateCustomizers = restTemplateCustomizers;
}
void setRestTemplateRequestCustomizers(List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
this.restTemplateRequestCustomizers = restTemplateRequestCustomizers;
}
/**
* Configure the specified {@link RestTemplateBuilder}. The builder can be further
* tuned and default settings can be overridden.
* @param builder the {@link RestTemplateBuilder} instance to configure
* @return the configured builder
*/
public RestTemplateBuilder configure(RestTemplateBuilder builder) {
if (this.requestFactoryBuilder != null) {
builder = builder.requestFactoryBuilder(this.requestFactoryBuilder);
}
if (this.requestFactorySettings != null) {
builder = builder.requestFactorySettings(this.requestFactorySettings);
}
if (this.httpMessageConverters != null) {
builder = builder.messageConverters(this.httpMessageConverters.getConverters());
}
builder = addCustomizers(builder, this.restTemplateCustomizers, RestTemplateBuilder::customizers);
builder = addCustomizers(builder, this.restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
return builder;
}
private <T> RestTemplateBuilder addCustomizers(RestTemplateBuilder builder, List<T> customizers,
BiFunction<RestTemplateBuilder, Collection<T>, RestTemplateBuilder> method) {
if (!ObjectUtils.isEmpty(customizers)) {
return method.apply(builder, customizers);
}
return builder;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for RestClient and RestTemplate.
*/
package org.springframework.boot.restclient.autoconfigure;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Web client utilities.
*/
package org.springframework.boot.restclient;

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.http.client.autoconfigure.AbstractHttpRequestFactoryProperties;
/**
* {@link AbstractHttpRequestFactoryProperties} for HTTP Service clients.
*
* @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @author Phillip Webb
* @since 4.0.0
*/
public abstract class AbstractHttpClientServiceProperties extends AbstractHttpRequestFactoryProperties {
/**
* Base url to set in the underlying HTTP client group. By default, set to
* {@code null}.
*/
private String baseUrl;
/**
* Default request headers for interface client group. By default, set to empty
* {@link Map}.
*/
private Map<String, List<String>> defaultHeader = new LinkedHashMap<>();
public String getBaseUrl() {
return this.baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public Map<String, List<String>> getDefaultHeader() {
return this.defaultHeader;
}
public void setDefaultHeader(Map<String, List<String>> defaultHeaders) {
this.defaultHeader = defaultHeaders;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties for HTTP Service clients.
*
* @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @author Phillip Webb
* @since 4.0.0
*/
@ConfigurationProperties("spring.http.client.service")
public class HttpClientServiceProperties extends AbstractHttpClientServiceProperties {
/**
* Group settings.
*/
private Map<String, Group> group = new LinkedHashMap<>();
public Map<String, Group> getGroup() {
return this.group;
}
public void setGroup(Map<String, Group> group) {
this.group = group;
}
/**
* Properties for a single HTTP Service client group.
*/
public static class Group extends AbstractHttpClientServiceProperties {
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;
import org.springframework.boot.http.client.autoconfigure.HttpClientProperties;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
import org.springframework.web.service.registry.ImportHttpServices;
/**
* AutoConfiguration for Spring HTTP Service clients.
* <p>
* This will result in the creation of blocking HTTP Service client beans defined by
* {@link ImportHttpServices @ImportHttpServices} annotations.
*
* @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @author Phillip Webb
* @since 4.0.0
*/
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestClientAutoConfiguration.class })
@ConditionalOnClass(RestClientAdapter.class)
@ConditionalOnBean(HttpServiceProxyRegistry.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@EnableConfigurationProperties(HttpClientServiceProperties.class)
public class HttpServiceClientAutoConfiguration implements BeanClassLoaderAware {
private ClassLoader beanClassLoader;
HttpServiceClientAutoConfiguration() {
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Bean
RestClientPropertiesHttpServiceGroupConfigurer restClientPropertiesHttpServiceGroupConfigurer(
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientProperties> httpClientProperties,
HttpClientServiceProperties serviceProperties,
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings) {
return new RestClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles,
httpClientProperties.getIfAvailable(), serviceProperties, clientFactoryBuilder,
clientHttpRequestFactorySettings);
}
@Bean
RestClientCustomizerHttpServiceGroupConfigurer restClientCustomizerHttpServiceGroupConfigurer(
ObjectProvider<RestClientCustomizer> customizers) {
return new RestClientCustomizerHttpServiceGroupConfigurer(customizers);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
/**
* {@link SpringBootCondition} that applies only when running in a non-reactive web
* application.
*
* @author Phillip Webb
*/
class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
private static final class ReactiveWebApplication {
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.restclient.RestClientCustomizer;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.registry.HttpServiceGroup;
/**
* A {@link RestClientHttpServiceGroupConfigurer} to apply auto-configured
* {@link RestClientCustomizer} beans to the group's {@link RestClient}.
*
* @author Olga Maciaszek-Sharma
* @author Phillip Webb
*/
class RestClientCustomizerHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
/**
* Allow user defined configurers to apply before / after ours.
*/
private static final int ORDER = 0;
private final ObjectProvider<RestClientCustomizer> customizers;
RestClientCustomizerHttpServiceGroupConfigurer(ObjectProvider<RestClientCustomizer> customizers) {
this.customizers = customizers;
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void configureGroups(Groups<RestClient.Builder> groups) {
groups.forEachClient(this::configureClient);
}
private void configureClient(HttpServiceGroup group, RestClient.Builder builder) {
this.customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.restclient.service.autoconfigure;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.autoconfigure.ClientHttpRequestFactories;
import org.springframework.boot.http.client.autoconfigure.HttpClientProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.registry.HttpServiceGroup;
/**
* A {@link RestClientHttpServiceGroupConfigurer} that configures the group and its
* underlying {@link RestClient} using {@link HttpClientProperties}.
*
* @author Olga Maciaszek-Sharma
* @author Phillip Webb
*/
class RestClientPropertiesHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
private final ClassLoader classLoader;
private final ObjectProvider<SslBundles> sslBundles;
private final HttpClientProperties clientProperties;
private final HttpClientServiceProperties serviceProperties;
private final ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder;
private final ObjectProvider<ClientHttpRequestFactorySettings> requestFactorySettings;
RestClientPropertiesHttpServiceGroupConfigurer(ClassLoader classLoader, ObjectProvider<SslBundles> sslBundles,
HttpClientProperties clientProperties, HttpClientServiceProperties serviceProperties,
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> requestFactorySettings) {
this.classLoader = classLoader;
this.sslBundles = sslBundles;
this.clientProperties = clientProperties;
this.serviceProperties = serviceProperties;
this.requestFactoryBuilder = requestFactoryBuilder;
this.requestFactorySettings = requestFactorySettings;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void configureGroups(Groups<RestClient.Builder> groups) {
groups.forEachClient(this::configureClient);
}
private void configureClient(HttpServiceGroup group, RestClient.Builder builder) {
HttpClientServiceProperties.Group groupProperties = this.serviceProperties.getGroup().get(group.name());
builder.requestFactory(getRequestFactory(groupProperties));
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serviceProperties::getBaseUrl).whenHasText().to(builder::baseUrl);
map.from(this.serviceProperties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders);
if (groupProperties != null) {
map.from(groupProperties::getBaseUrl).whenHasText().to(builder::baseUrl);
map.from(groupProperties::getDefaultHeader).as(this::putAllHeaders).to(builder::defaultHeaders);
}
}
private Consumer<HttpHeaders> putAllHeaders(Map<String, List<String>> defaultHeaders) {
return (httpHeaders) -> httpHeaders.putAll(defaultHeaders);
}
private ClientHttpRequestFactory getRequestFactory(HttpClientServiceProperties.Group groupProperties) {
ClientHttpRequestFactories factories = new ClientHttpRequestFactories(this.sslBundles, groupProperties,
this.serviceProperties, this.clientProperties);
ClientHttpRequestFactoryBuilder<?> builder = this.requestFactoryBuilder
.getIfAvailable(() -> factories.builder(this.classLoader));
ClientHttpRequestFactorySettings settings = this.requestFactorySettings.getIfAvailable(factories::settings);
return builder.build(settings);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-Configuration for Spring's Blocking HTTP Service Interface Clients.
*/
package org.springframework.boot.restclient.service.autoconfigure;

View File

@@ -0,0 +1,3 @@
org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration
org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration
org.springframework.boot.restclient.service.autoconfigure.HttpServiceClientAutoConfiguration