调用 REST 服务

Spring Boot 提供了多种便捷的方式来调用远程 REST 服务。 如果您正在开发非阻塞响应式应用程序并且使用的是 Spring WebFlux,那么您可以使用 WebClient。 如果您更倾向于命令式 API,那么您可以使用 RestClientRestTemplatespring-doc.cadn.net.cn

WebClient

如果您的类路径上有 Spring WebFlux,我们建议您使用 WebClient 来调用远程 REST 服务。 WebClient 接口提供了函数式风格的 API,并且是完全响应式的。 您可以在 Spring 框架文档的专用 部分 中了解更多关于 WebClient 的信息。spring-doc.cadn.net.cn

如果您不是在编写响应式 Spring WebFlux 应用程序,则可以使用 RestClient 而不是 WebClient。 这提供了类似的功能性 API,但是是命令式的而非响应式的。

Spring Boot 会为您创建并预配置一个原型 WebClient.Builder Bean。 强烈建议您将其注入到您的组件中,并使用它来创建 WebClient 实例。 Spring Boot 正在配置该构建器以共享 HTTP 资源,并以与服务器相同的方式反映编解码器设置(请参阅 WebFlux HTTP 编解码器自动配置)等更多功能。spring-doc.cadn.net.cn

以下代码展示了一个典型示例:spring-doc.cadn.net.cn

import reactor.core.publisher.Mono;

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {

	private final WebClient webClient;

	public MyService(WebClient.Builder webClientBuilder) {
		this.webClient = webClientBuilder.baseUrl("https://example.org").build();
	}

	public Mono<Details> someRestCall(String name) {
		return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
	}

}
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder) {

	private val webClient: WebClient

	init {
		webClient = webClientBuilder.baseUrl("https://example.org").build()
	}

	fun someRestCall(name: String): Mono<Details> {
		return webClient.get().uri("/{name}/details", name)
				.retrieve().bodyToMono(Details::class.java)
	}

}

WebClient 运行时

Spring Boot 将根据应用程序类路径上可用的库,自动检测使用哪个 ClientHttpConnector 来驱动 WebClient。 按优先级顺序,支持以下客户端:spring-doc.cadn.net.cn

  1. Reactor Nettyspring-doc.cadn.net.cn

  2. Jetty RS 客户端spring-doc.cadn.net.cn

  3. Apache HttpClientspring-doc.cadn.net.cn

  4. JDK HttpClientspring-doc.cadn.net.cn

如果类路径上存在多个客户端,将使用优先级最高的客户端。spring-doc.cadn.net.cn

spring-boot-starter-webflux Starters默认依赖于 io.projectreactor.netty:reactor-netty,它同时提供了服务器端和客户端的实现。 如果你选择使用 Jetty 作为响应式服务器,则应添加 Jetty 响应式 HTTP 客户端库 org.eclipse.jetty:jetty-reactive-httpclient 的依赖。 对服务器和客户端使用相同的技术具有一定的优势,因为它会自动在客户端和服务器之间共享 HTTP 资源。spring-doc.cadn.net.cn

开发人员可以通过提供自定义的 ReactorResourceFactoryJettyResourceFactory Bean 来覆盖 Jetty 和 Reactor Netty 的资源配置 - 这将同时应用于客户端和服务器端。spring-doc.cadn.net.cn

如果您希望覆盖客户端的该选择,可以定义自己的 ClientHttpConnector Bean,并完全控制客户端配置。spring-doc.cadn.net.cn

全局 HTTP 连接器配置

如果自动检测到的 ClientHttpConnector 无法满足您的需求,您可以使用 spring.http.clients.reactive.connector 属性来选择特定的连接器。 例如,如果您的类路径中包含 Reactor Netty,但您更倾向于使用 Jetty 的 HttpClient,则可以添加以下内容:spring-doc.cadn.net.cn

spring.http.clients.reactive.connector=jetty
spring:
  http:
    clients:
      reactive:
        connector: jetty
你也可以使用全局配置属性,这些属性适用于所有 HTTP 客户端。

对于更复杂的自定义,您可以使用 ClientHttpConnectorBuilderCustomizer 或声明您自己的 ClientHttpConnectorBuilder Bean,这将导致自动配置回退。 当您需要自定义底层 HTTP 库的某些内部组件时,这非常有用。spring-doc.cadn.net.cn

例如,以下代码将使用配置了特定 ProxySelector 的 JDK 客户端:spring-doc.cadn.net.cn

import java.net.ProxySelector;

import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyConnectorHttpConfiguration {

	@Bean
	ClientHttpConnectorBuilder<?> clientHttpConnectorBuilder(ProxySelector proxySelector) {
		return ClientHttpConnectorBuilder.jdk().withHttpClientCustomizer((builder) -> builder.proxy(proxySelector));
	}

}
import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.net.ProxySelector

@Configuration(proxyBeanMethods = false)
class MyConnectorHttpConfiguration {

	@Bean
	fun clientHttpConnectorBuilder(proxySelector: ProxySelector): ClientHttpConnectorBuilder<*> {
		return ClientHttpConnectorBuilder.jdk().withHttpClientCustomizer { builder -> builder.proxy(proxySelector) }
	}

}

WebClient 自定义

根据您希望自定义适用的范围,主要有三种 WebClient 自定义方法。spring-doc.cadn.net.cn

为了尽可能缩小自定义的范围,请注入自动配置的 WebClient.Builder,然后根据需要调用其方法。 WebClient.Builder 实例是有状态的:对构建器的任何更改都会反映在随后使用它创建的所有客户端中。 如果您想使用同一个构建器创建多个客户端,也可以考虑使用 WebClient.Builder other = builder.clone(); 克隆该构建器。spring-doc.cadn.net.cn

若要对所有 WebClient.Builder 实例进行应用范围的附加自定义,您可以声明 WebClientCustomizer Bean,并在注入点本地修改 WebClient.Builderspring-doc.cadn.net.cn

最后,您可以回退到原始 API 并使用 WebClient.create()。 在这种情况下,不会应用任何自动配置或 WebClientCustomizerspring-doc.cadn.net.cn

WebClient SSL 支持

如果您需要在 ClientHttpConnector(由 WebClient 使用)上配置自定义 SSL,可以注入一个 WebClientSsl 实例,该实例可与构建器的 apply 方法配合使用。spring-doc.cadn.net.cn

WebClientSsl 接口可让您访问在 application.propertiesapplication.yaml 文件中定义的任何 SSL 捆绑包spring-doc.cadn.net.cn

以下代码展示了一个典型示例:spring-doc.cadn.net.cn

import reactor.core.publisher.Mono;

import org.springframework.boot.webclient.autoconfigure.WebClientSsl;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class MyService {

	private final WebClient webClient;

	public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
		this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
	}

	public Mono<Details> someRestCall(String name) {
		return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
	}

}
import org.springframework.boot.webclient.autoconfigure.WebClientSsl
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
class MyService(webClientBuilder: WebClient.Builder, ssl: WebClientSsl) {

	private val webClient: WebClient

	init {
		webClient = webClientBuilder.baseUrl("https://example.org")
				.apply(ssl.fromBundle("mybundle")).build()
	}

	fun someRestCall(name: String): Mono<Details> {
		return webClient.get().uri("/{name}/details", name)
				.retrieve().bodyToMono(Details::class.java)
	}

}

REST 客户端

如果您的应用程序未使用 Spring WebFlux 或 Project Reactor,我们建议您使用 RestClient 来调用远程 REST 服务。spring-doc.cadn.net.cn

RestClient 接口提供了一种函数式风格的命令式 API。spring-doc.cadn.net.cn

Spring Boot 会为您创建并预配置一个原型 RestClient.Builder Bean。 强烈建议您将其注入到您的组件中,并使用它来创建 RestClient 实例。 Spring Boot 会使用 HttpMessageConverters 和一个合适的 ClientHttpRequestFactory 来配置该构建器。spring-doc.cadn.net.cn

以下代码展示了一个典型示例:spring-doc.cadn.net.cn

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class MyService {

	private final RestClient restClient;

	public MyService(RestClient.Builder restClientBuilder) {
		this.restClient = restClientBuilder.baseUrl("https://example.org").build();
	}

	public Details someRestCall(String name) {
		return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
	}

}
import org.springframework.boot.docs.io.restclient.restclient.ssl.Details
import org.springframework.stereotype.Service
import org.springframework.web.client.RestClient

@Service
class MyService(restClientBuilder: RestClient.Builder) {

	private val restClient: RestClient

	init {
		restClient = restClientBuilder.baseUrl("https://example.org").build()
	}

	fun someRestCall(name: String): Details {
		return restClient.get().uri("/{name}/details", name)
				.retrieve().body(Details::class.java)!!
	}

}

RestClient 自定义

根据您希望自定义适用的范围,主要有三种 RestClient 自定义方法。spring-doc.cadn.net.cn

为了尽可能缩小自定义的范围,请注入自动配置的 RestClient.Builder,然后根据需要调用其方法。 RestClient.Builder 实例是有状态的:对构建器的任何更改都会反映在随后使用它创建的所有客户端中。 如果您想使用同一个构建器创建多个客户端,也可以考虑使用 RestClient.Builder other = builder.clone(); 克隆该构建器。spring-doc.cadn.net.cn

若要对所有 RestClient.Builder 实例进行应用范围的附加自定义,您可以声明 RestClientCustomizer Bean,并在注入点本地修改 RestClient.Builderspring-doc.cadn.net.cn

最后,您可以回退到原始 API 并使用 RestClient.create()。 在这种情况下,不会应用任何自动配置或 RestClientCustomizerspring-doc.cadn.net.cn

你也可以更改全局 HTTP 客户端配置

RestClient SSL 支持

如果您需要在 ClientHttpRequestFactory(由 RestClient 使用)上配置自定义 SSL,可以注入一个 RestClientSsl 实例,该实例可与构建器的 apply 方法配合使用。spring-doc.cadn.net.cn

RestClientSsl 接口可让您访问在 application.propertiesapplication.yaml 文件中定义的任何 SSL 捆绑包spring-doc.cadn.net.cn

以下代码展示了一个典型示例:spring-doc.cadn.net.cn

import org.springframework.boot.restclient.autoconfigure.RestClientSsl;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class MyService {

	private final RestClient restClient;

	public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
		this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
	}

	public Details someRestCall(String name) {
		return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
	}

}
import org.springframework.boot.docs.io.restclient.restclient.ssl.settings.Details
import org.springframework.boot.restclient.autoconfigure.RestClientSsl
import org.springframework.stereotype.Service
import org.springframework.web.client.RestClient

@Service
class MyService(restClientBuilder: RestClient.Builder, ssl: RestClientSsl) {

	private val restClient: RestClient

	init {
		restClient = restClientBuilder.baseUrl("https://example.org")
				.apply(ssl.fromBundle("mybundle")).build()
	}

	fun someRestCall(name: String): Details {
		return restClient.get().uri("/{name}/details", name)
				.retrieve().body(Details::class.java)!!
	}

}

如果您需要在 SSL 捆绑包之外应用其他自定义配置,可以将 HttpClientSettings 类与 ClientHttpRequestFactoryBuilder 一起使用:spring-doc.cadn.net.cn

import java.time.Duration;

import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.HttpClientSettings;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class MyService {

	private final RestClient restClient;

	public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) {
		HttpClientSettings settings = HttpClientSettings.ofSslBundle(sslBundles.getBundle("mybundle"))
			.withReadTimeout(Duration.ofMinutes(2));
		ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder.detect().build(settings);
		this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build();
	}

	public Details someRestCall(String name) {
		return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
	}

}
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.HttpClientSettings
import org.springframework.boot.ssl.SslBundles
import org.springframework.stereotype.Service
import org.springframework.web.client.RestClient
import java.time.Duration

@Service
class MyService(restClientBuilder: RestClient.Builder, sslBundles: SslBundles) {

	private val restClient: RestClient

	init {
		val settings = HttpClientSettings.defaults()
				.withReadTimeout(Duration.ofMinutes(2))
				.withSslBundle(sslBundles.getBundle("mybundle"))
		val requestFactory = ClientHttpRequestFactoryBuilder.detect().build(settings);
		restClient = restClientBuilder
				.baseUrl("https://example.org")
				.requestFactory(requestFactory).build()
	}

	fun someRestCall(name: String): Details {
		return restClient.get().uri("/{name}/details", name).retrieve().body(Details::class.java)!!
	}

}

RestTemplate

Spring Framework 的 RestTemplate 类早于 RestClient,是许多应用程序调用远程 REST 服务的经典方式。 当您拥有不希望迁移到 RestClient 的现有代码时,或者因为您已经熟悉 RestTemplate API 时,您可能会选择使用 RestTemplatespring-doc.cadn.net.cn

由于 RestTemplate 实例在通常使用前需要自定义,Spring Boot 不提供任何单一自动配置的 RestTemplate Bean。 不过,它确实会自动配置一个 RestTemplateBuilder,可在需要时用于创建 RestTemplate 实例。 自动配置的 RestTemplateBuilder 确保将合理的 HttpMessageConverters 和适当的 ClientHttpRequestFactory 应用于 RestTemplate 实例。spring-doc.cadn.net.cn

以下代码展示了一个典型示例:spring-doc.cadn.net.cn

import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {

	private final RestTemplate restTemplate;

	public MyService(RestTemplateBuilder restTemplateBuilder) {
		this.restTemplate = restTemplateBuilder.build();
	}

	public Details someRestCall(String name) {
		return this.restTemplate.getForObject("/{name}/details", Details.class, name);
	}

}
import org.springframework.boot.restclient.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder) {

	private val restTemplate: RestTemplate

	init {
		restTemplate = restTemplateBuilder.build()
	}

	fun someRestCall(name: String): Details {
		return restTemplate.getForObject("/{name}/details", Details::class.java, name)!!
	}

}

RestTemplateBuilder 包含许多实用方法,可用于快速配置 RestTemplate。 例如,要添加 BASIC 身份验证支持,您可以使用 builder.basicAuthentication("user", "password").build()spring-doc.cadn.net.cn

RestTemplate 自定义

根据您希望自定义适用的范围,主要有三种 RestTemplate 自定义方法。spring-doc.cadn.net.cn

为了将任何自定义的范围尽可能缩小,请注入自动配置的 RestTemplateBuilder,然后根据需要调用其方法。 每次方法调用都会返回一个新的 RestTemplateBuilder 实例,因此自定义仅影响该构建器的此次使用。spring-doc.cadn.net.cn

要进行应用程序范围的附加自定义,请使用 RestTemplateCustomizer Bean。 所有此类 Bean 都会自动注册到自动配置的 RestTemplateBuilder 中,并应用于使用它构建的任何模板。spring-doc.cadn.net.cn

以下示例展示了一个自定义器,它配置了对除 192.168.0.5 之外的所有主机使用代理:spring-doc.cadn.net.cn

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;

import org.springframework.boot.restclient.RestTemplateCustomizer;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class MyRestTemplateCustomizer implements RestTemplateCustomizer {

	@Override
	public void customize(RestTemplate restTemplate) {
		HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
		HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
		restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
	}

	static class CustomRoutePlanner extends DefaultProxyRoutePlanner {

		CustomRoutePlanner(HttpHost proxy) {
			super(proxy);
		}

		@Override
		protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {
			if (target.getHostName().equals("192.168.0.5")) {
				return null;
			}
			return super.determineProxy(target, context);
		}

	}

}
import org.apache.hc.client5.http.classic.HttpClient
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner
import org.apache.hc.client5.http.routing.HttpRoutePlanner
import org.apache.hc.core5.http.HttpException
import org.apache.hc.core5.http.HttpHost
import org.apache.hc.core5.http.protocol.HttpContext
import org.springframework.boot.restclient.RestTemplateCustomizer
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate

class MyRestTemplateCustomizer : RestTemplateCustomizer {

	override fun customize(restTemplate: RestTemplate) {
		val routePlanner: HttpRoutePlanner = CustomRoutePlanner(HttpHost("proxy.example.com"))
		val httpClient: HttpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build()
		restTemplate.requestFactory = HttpComponentsClientHttpRequestFactory(httpClient)
	}

	internal class CustomRoutePlanner(proxy: HttpHost?) : DefaultProxyRoutePlanner(proxy) {

		@Throws(HttpException::class)
		public override fun determineProxy(target: HttpHost, context: HttpContext): HttpHost? {
			if (target.hostName == "192.168.0.5") {
				return null
			}
			return  super.determineProxy(target, context)
		}

	}

}

最后,您可以定义自己的 RestTemplateBuilder Bean。 这样做将替换自动配置的构建器。 如果您希望任何 RestTemplateCustomizer Bean 应用于您的自定义构建器(就像自动配置所做的那样),请使用 RestTemplateBuilderConfigurer 进行配置。 以下示例展示了一个 RestTemplateBuilder,其功能与 Spring Boot 的自动配置一致,但额外指定了自定义的连接超时和读取超时:spring-doc.cadn.net.cn

import java.time.Duration;

import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.autoconfigure.RestTemplateBuilderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {

	@Bean
	public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
		return configurer.configure(new RestTemplateBuilder())
			.connectTimeout(Duration.ofSeconds(5))
			.readTimeout(Duration.ofSeconds(2));
	}

}
import org.springframework.boot.restclient.autoconfigure.RestTemplateBuilderConfigurer
import org.springframework.boot.restclient.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

@Configuration(proxyBeanMethods = false)
class MyRestTemplateBuilderConfiguration {

	@Bean
	fun restTemplateBuilder(configurer: RestTemplateBuilderConfigurer): RestTemplateBuilder {
		return configurer.configure(RestTemplateBuilder()).connectTimeout(Duration.ofSeconds(5))
			.readTimeout(Duration.ofSeconds(2))
	}

}

最极端(且极少使用)的选项是不使用配置器,自行创建您自己的 RestTemplateBuilder bean。 除了替换自动配置的构建器之外,这还会阻止任何 RestTemplateCustomizer bean 被使用。spring-doc.cadn.net.cn

你也可以更改全局 HTTP 客户端配置

RestTemplate SSL 支持

如果您需要在 RestTemplate 上配置自定义 SSL,可以按照本示例所示,将 SSL 捆绑包 应用到 RestTemplateBuilderspring-doc.cadn.net.cn

import org.springframework.boot.docs.io.restclient.resttemplate.Details;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {

	private final RestTemplate restTemplate;

	public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
		this.restTemplate = restTemplateBuilder.sslBundle(sslBundles.getBundle("mybundle")).build();
	}

	public Details someRestCall(String name) {
		return this.restTemplate.getForObject("/{name}/details", Details.class, name);
	}

}
import org.springframework.boot.docs.io.restclient.resttemplate.Details
import org.springframework.boot.ssl.SslBundles
import org.springframework.boot.restclient.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class MyService(restTemplateBuilder: RestTemplateBuilder, sslBundles: SslBundles) {

    private val restTemplate: RestTemplate

    init {
        restTemplate = restTemplateBuilder.sslBundle(sslBundles.getBundle("mybundle")).build()
    }

    fun someRestCall(name: String): Details {
        return restTemplate.getForObject("/{name}/details", Details::class.java, name)!!
    }

}

RestClient 和 RestTemplate 的 HTTP 客户端检测

Spring Boot 将根据应用程序类路径上可用的库,自动检测与 RestClientRestTemplate 一起使用的 HTTP 客户端。 按优先顺序,支持以下客户端:spring-doc.cadn.net.cn

  1. Apache HttpClientspring-doc.cadn.net.cn

  2. Jetty HttpClientspring-doc.cadn.net.cn

  3. Reactor Netty Http客户端spring-doc.cadn.net.cn

  4. JDK 客户端(java.net.http.HttpClientspring-doc.cadn.net.cn

  5. 简单的 JDK 客户端(java.net.HttpURLConnectionspring-doc.cadn.net.cn

如果类路径上存在多个客户端,且未提供全局配置,则将使用优先级最高的客户端。spring-doc.cadn.net.cn

全局 HTTP 客户端配置

如果自动检测的 HTTP 客户端无法满足您的需求,您可以使用 spring.http.clients.imperative.factory 属性来选择特定的工厂。 例如,如果您的类路径中存在 Apache HttpClient,但您更倾向于使用 Jetty 的 HttpClient,则可以添加以下内容:spring-doc.cadn.net.cn

spring.http.clients.imperative.factory=jetty
spring:
  http:
    clients:
      imperative:
        factory: jetty
你也可以使用全局配置属性,这些属性适用于所有 HTTP 客户端。

对于更复杂的自定义,您可以使用 ClientHttpRequestFactoryBuilderCustomizer 或声明您自己的 ClientHttpRequestFactoryBuilder Bean,这将导致自动配置回退。 当您需要自定义底层 HTTP 库的某些内部组件时,这非常有用。spring-doc.cadn.net.cn

例如,以下代码将使用配置了特定 ProxySelector 的 JDK 客户端:spring-doc.cadn.net.cn

import java.net.ProxySelector;

import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyClientHttpConfiguration {

	@Bean
	ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder(ProxySelector proxySelector) {
		return ClientHttpRequestFactoryBuilder.jdk()
			.withHttpClientCustomizer((builder) -> builder.proxy(proxySelector));
	}

}
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.net.ProxySelector

@Configuration(proxyBeanMethods = false)
class MyClientHttpConfiguration {

	@Bean
	fun clientHttpRequestFactoryBuilder(proxySelector: ProxySelector): ClientHttpRequestFactoryBuilder<*> {
		return ClientHttpRequestFactoryBuilder.jdk()
				.withHttpClientCustomizer { builder -> builder.proxy(proxySelector) }
	}

}

API 版本控制

WebClientRestClient 均支持发起带版本的远程 HTTP 调用,以便 API 能够随时间演进。 通常,这涉及发送一个 HTTP 头、查询参数或 URL 路径段,以指明应使用的 API 版本。spring-doc.cadn.net.cn

您可以使用 WebClient.BuilderRestClient.Builder 上的方法来配置 API 版本控制。spring-doc.cadn.net.cn

服务器端也支持 API 版本控制。 有关详细信息,请参阅 Spring MVCSpring WebFlux 章节。
服务器端的 API 版本控制配置不会被用于自动配置客户端。 需要使用 API 版本控制策略的客户端(通常用于测试)必须显式地进行配置。

HTTP 服务接口客户端

除了直接使用 RestClientWebClient 调用 HTTP 服务外,还可以使用带注解的 Java 接口来调用它们。spring-doc.cadn.net.cn

HTTP 服务接口通过定义使用 @HttpExchange 注解的方法,或更典型地使用方法特定的变体(@GetExchange@PostExchange@DeleteExchange 等)来定义服务契约。spring-doc.cadn.net.cn

例如,以下代码定义了一个用于“echo”API的HTTP服务,该服务将返回一个包含请求回显内容的JSON对象。spring-doc.cadn.net.cn

import java.util.Map;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;

@HttpExchange(url = "https://echo.zuplo.io")
public interface EchoService {

	@PostExchange
	Map<?, ?> echo(@RequestBody Map<String, String> message);

}
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.service.annotation.HttpExchange
import org.springframework.web.service.annotation.PostExchange

@HttpExchange(url = "https://echo.zuplo.io")
interface EchoService {

	@PostExchange
	fun echo(@RequestBody message: Map<String, String>): Map<*, *>

}

有关如何开发 HTTP 服务接口客户端的更多详细信息,请参阅Spring Framework 参考文档spring-doc.cadn.net.cn

导入 HTTP 服务

为了将 HTTP 服务接口用作客户端,您需要导入它。 实现这一点的一种方法是在您的主应用程序类上使用 @ImportHttpServices 注解。 您可以使用该注解导入特定的类,或者扫描特定包中的类以进行导入。spring-doc.cadn.net.cn

例如,以下配置将在 com.example.myclients 包中扫描 HTTP 服务接口:spring-doc.cadn.net.cn

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.service.registry.ImportHttpServices;

@SpringBootApplication
@ImportHttpServices(basePackages = "com.example.myclients")
public class MyApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyApplication.class, args);
	}

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.service.registry.ImportHttpServices

@SpringBootApplication
@ImportHttpServices(basePackages = ["com.example.myclients"])
class MyApplication

fun main(args: Array<String>) {
	runApplication<MyApplication>(*args)
}

服务客户端组

@HttpExchange 注解中硬编码绝对 URL 在生产应用中通常并不理想。 相反,您通常希望在代码中为 HTTP Service 客户端指定一个逻辑名称,然后根据该名称从配置属性中查找对应的 URL。spring-doc.cadn.net.cn

HTTP 服务客户端通过将它们注册到命名组中来实现这一点。 HTTP 服务组是一组 HTTP 服务接口的集合,这些接口都具有共同的特性。spring-doc.cadn.net.cn

例如,我们可能希望定义一个“echo”组,用于调用 https://echo.zuplo.io 的 HTTP 服务客户端。spring-doc.cadn.net.cn

HTTP 服务组不仅可以用于定义 URL。 例如,您的服务组可以定义连接超时和 SSL 设置。 您还可以将客户端自定义逻辑关联到一个服务组,例如添加代码以插入所需的授权头信息。

当使用 @ImportHttpServices 将 HTTP 服务接口与组关联时,您可以使用 group 属性。spring-doc.cadn.net.cn

例如,如果我们假设上面的示例以如下方式组织:即 com.example.myclients 包中的所有 HTTP 服务接口都属于 echo 组。 我们首先从服务接口中移除硬编码的 URL:spring-doc.cadn.net.cn

import java.util.Map;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.PostExchange;

public interface EchoService {

	@PostExchange
	Map<?, ?> echo(@RequestBody Map<String, String> message);

}
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.service.annotation.HttpExchange
import org.springframework.web.service.annotation.PostExchange

interface EchoService {

	@PostExchange
	fun echo(@RequestBody message: Map<String, String>): Map<*, *>

}

然后我们可以编写:spring-doc.cadn.net.cn

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.service.registry.ImportHttpServices;

@SpringBootApplication
@ImportHttpServices(group = "echo", basePackages = "com.example.myclients")
public class MyApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyApplication.class, args);
	}

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.service.registry.ImportHttpServices

@SpringBootApplication
@ImportHttpServices(group = "echo", basePackages = ["com.example.myclients"])
class MyApplication

fun main(args: Array<String>) {
	runApplication<MyApplication>(*args)
}

最后,我们可以使用一个 base-url 属性,将 echo 组链接到一个实际的 URL:spring-doc.cadn.net.cn

spring.http.serviceclient.echo.base-url=https://echo.zuplo.io
spring:
  http:
    serviceclient:
      echo:
        base-url: "https://echo.zuplo.io"
如果你未指定组,HTTP 服务客户端将关联到名为“default”的组。

如果在同一个包中有多个需要关联到不同组的 HTTP 服务接口,您可以单独列出它们。 @ImportHttpServices 是可重复的,而 types 属性允许您导入单独的类。spring-doc.cadn.net.cn

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.service.registry.ImportHttpServices;

@SpringBootApplication
@ImportHttpServices(group = "echo", types = EchoService.class)
@ImportHttpServices(group = "other", types = OtherService.class)
public class MyApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyApplication.class, args);
	}

}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.service.registry.ImportHttpServices

@SpringBootApplication
@ImportHttpServices(group = "echo", types = [EchoService::class])
@ImportHttpServices(group = "other", types = [OtherService::class])
class MyApplication

fun main(args: Array<String>) {
	runApplication<MyApplication>(*args)
}

配置属性

HTTP 服务的配置属性可在 spring.http.serviceclient.<group-name> 下指定:spring-doc.cadn.net.cn

您可以使用属性来配置诸如以下方面的内容:spring-doc.cadn.net.cn

你也可以使用全局配置属性,这些属性适用于所有 HTTP 客户端。

例如,以下属性将:spring-doc.cadn.net.cn

spring.http.clients.connect-timeout=1s
spring.http.serviceclient.echo.base-url=https://echo.zuplo.io
spring.http.serviceclient.echo.read-timeout=2s
spring.http.serviceclient.echo.apiversion.default=1.0.0
spring.http.serviceclient.echo.apiversion.insert.header=X-Version
spring:
  http:
    clients:
      connect-timeout: 1s
    serviceclient:
      echo:
        base-url: "https://echo.zuplo.io"
        read-timeout: 2s
        apiversion:
          default: 1.0.0
          insert:
            header: X-Version

自定义

如果您需要超越基本属性来自定义 HTTP 服务客户端,可以使用 HTTP 服务组配置器。 对于由 RestClient 支持的 HTTP 服务客户端,您可以声明一个实现 RestClientHttpServiceGroupConfigurer 的 Bean。 对于由 WebClient 支持的 HTTP 服务客户端,您可以声明一个实现 WebClientHttpServiceGroupConfigurer 的 Bean。spring-doc.cadn.net.cn

两者的工作方式相同,并将由 Spring Boot 的自动配置自动应用。spring-doc.cadn.net.cn

例如,以下配置将添加一个组自定义器(group customizer),该自定义器会在每个传出请求中添加一个包含组名称的 HTTP 头部:spring-doc.cadn.net.cn

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyHttpServiceGroupConfiguration {

	@Bean
	RestClientHttpServiceGroupConfigurer myHttpServiceGroupConfigurer() {
		return (groups) -> groups.forEachClient((group, clientBuilder) -> {
			String groupName = group.name();
			clientBuilder.defaultHeader("service-group", groupName);
		});
	}

}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer

@Configuration(proxyBeanMethods = false)
class MyHttpServiceGroupConfiguration {

	@Bean
	fun myHttpServiceGroupConfigurer(): RestClientHttpServiceGroupConfigurer {
		return RestClientHttpServiceGroupConfigurer { groups ->
			groups.forEachClient { group, clientBuilder ->
				val groupName = group.name()
				clientBuilder.defaultHeader("service-group", groupName)
			}
		}
	}

}

高级配置

除了 @ImportHttpServices 注解之外,Spring Framework 还提供了一个 AbstractHttpServiceRegistrar 类。 您可以 @Import 该类的一个自定义扩展来执行编程式配置。 更多详情,请参阅 Spring Framework 参考文档spring-doc.cadn.net.cn

无论你使用哪种方法注册 HTTP 服务客户端,Spring Boot 的支持都保持不变。spring-doc.cadn.net.cn

将全局配置应用于所有 HTTP 客户端

无论使用何种底层技术,所有 HTTP 客户端都具有可配置的通用设置。spring-doc.cadn.net.cn

包括以下内容:spring-doc.cadn.net.cn

这些通用设置由 HttpClientSettings 类表示,该类可以传递给 ClientHttpConnectorBuilderClientHttpRequestFactoryBuilderbuild(…​) 方法。spring-doc.cadn.net.cn

如果你想将相同的配置应用于所有自动配置的客户端,可以使用 spring.http.clients 属性来实现:spring-doc.cadn.net.cn

spring.http.clients.connect-timeout=2s
spring.http.clients.read-timeout=1s
spring.http.clients.redirects=dont-follow
spring:
  http:
    clients:
      connect-timeout: 2s
      read-timeout: 1s
      redirects: dont-follow