调用 REST 服务

Spring Boot 提供了多种方便的方式来调用远程 REST 服务。如果你正在开发一个非阻塞响应式应用程序并使用 Spring WebFlux,那么可以使用 WebClient。如果你更喜欢阻塞 API,那么可以使用 RestClientRestTemplate

WebClient

如果你的 classpath 中包含 Spring WebFlux,我们建议你使用 WebClient 来调用远程 REST 服务。WebClient 接口提供函数式风格的 API,并且是完全响应式的。你可以在 Spring Framework 文档的专用章节中了解更多关于 WebClient 的信息。

如果你不是在编写响应式 Spring WebFlux 应用程序,则可以使用 RestClient 代替 WebClient。它提供了类似的函数式 API,但是是阻塞的而不是响应式的。

Spring Boot 为你创建并预配置了一个原型 WebClient.Builder bean。强烈建议将其注入到你的组件中,并使用它来创建 WebClient 实例。Spring Boot 配置该 builder 以共享 HTTP 资源并以与服务器相同的方式反映编解码器设置(参见 WebFlux HTTP 编解码器自动配置),等等。

以下代码显示了一个典型示例

  • Java

  • Kotlin

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 Runtime

Spring Boot 会根据应用程序 classpath 中可用的库自动检测使用哪个 ClientHttpConnector 来驱动 WebClient。按照偏好顺序,支持以下客户端

  1. Reactor Netty

  2. Jetty RS 客户端

  3. Apache HttpClient

  4. JDK HttpClient

如果 classpath 中有多个客户端可用,将使用优先级最高的客户端。

默认情况下,spring-boot-starter-webflux 启动器依赖于 io.projectreactor.netty:reactor-netty,它带来了服务器和客户端的实现。如果你选择使用 Jetty 作为响应式服务器,则应添加对 Jetty Reactive HTTP 客户端库 org.eclipse.jetty:jetty-reactive-httpclient 的依赖。服务器和客户端使用相同的技术有其优势,因为它会在客户端和服务器之间自动共享 HTTP 资源。

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

如果你想为客户端覆盖此选择,可以定义自己的 ClientHttpConnector bean,并完全控制客户端配置。

你可以在 Spring Framework 参考文档中了解更多关于 WebClient 配置选项的信息。

WebClient 自定义

WebClient 自定义主要有三种方法,具体取决于你希望自定义应用于多大范围。

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

要对所有 WebClient.Builder 实例进行应用范围的附加自定义,可以声明 WebClientCustomizer bean,并在注入点本地更改 WebClient.Builder

最后,你可以退回到原始 API 并使用 WebClient.create()。在这种情况下,不会应用自动配置或 WebClientCustomizer

WebClient SSL 支持

如果你需要在 ClientHttpConnector 使用的 WebClient 上进行自定义 SSL 配置,可以注入一个 WebClientSsl 实例,该实例可与 builder 的 apply 方法一起使用。

WebClientSsl 接口提供了访问你在 application.propertiesapplication.yaml 文件中定义的任何 SSL 捆绑包的能力。

以下代码显示了一个典型示例

  • Java

  • Kotlin

import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.reactive.function.client.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.autoconfigure.web.reactive.function.client.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)
	}

}

RestClient

如果你的应用程序中未使用 Spring WebFlux 或 Project Reactor,我们建议你使用 RestClient 来调用远程 REST 服务。

RestClient 接口提供函数式风格的阻塞 API。

Spring Boot 为你创建并预配置了一个原型 RestClient.Builder bean。强烈建议将其注入到你的组件中,并使用它来创建 RestClient 实例。Spring Boot 配置该 builder 使用 HttpMessageConverters 和适当的 ClientHttpRequestFactory

以下代码显示了一个典型示例

  • Java

  • Kotlin

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 自定义主要有三种方法,具体取决于你希望自定义应用于多大范围。

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

要对所有 RestClient.Builder 实例进行应用范围的附加自定义,可以声明 RestClientCustomizer bean,并在注入点本地更改 RestClient.Builder

最后,你可以退回到原始 API 并使用 RestClient.create()。在这种情况下,不会应用自动配置或 RestClientCustomizer

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

RestClient SSL 支持

如果你需要在 ClientHttpRequestFactory 使用的 RestClient 上进行自定义 SSL 配置,可以注入一个 RestClientSsl 实例,该实例可与 builder 的 apply 方法一起使用。

RestClientSsl 接口提供了访问你在 application.propertiesapplication.yaml 文件中定义的任何 SSL 捆绑包的能力。

以下代码显示了一个典型示例

  • Java

  • Kotlin

import org.springframework.boot.autoconfigure.web.client.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.autoconfigure.web.client.RestClientSsl
import org.springframework.boot.docs.io.restclient.restclient.ssl.settings.Details
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 捆绑包之外还需要应用其他自定义,可以使用 ClientHttpRequestFactorySettings 类与 ClientHttpRequestFactoryBuilder

  • Java

  • Kotlin

import java.time.Duration;

import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
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) {
		ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings
			.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.ClientHttpRequestFactorySettings;
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 = ClientHttpRequestFactorySettings.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 时,可以选择使用 RestTemplate

由于 RestTemplate 实例在使用前通常需要自定义,Spring Boot 不提供任何单一的自动配置 RestTemplate bean。但是,它会自动配置一个 RestTemplateBuilder,可以用于在需要时创建 RestTemplate 实例。自动配置的 RestTemplateBuilder 确保将合理的 HttpMessageConverters 和适当的 ClientHttpRequestFactory 应用于 RestTemplate 实例。

以下代码显示了一个典型示例

  • Java

  • Kotlin

import org.springframework.boot.web.client.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.web.client.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()

RestTemplate 自定义

RestTemplate 自定义主要有三种方法,具体取决于你希望自定义应用于多大范围。

为了使任何自定义的应用范围尽可能小,请注入自动配置的 RestTemplateBuilder,然后根据需要调用其方法。每个方法调用都返回一个新的 RestTemplateBuilder 实例,因此自定义仅影响本次 builder 的使用。

要进行应用范围的附加自定义,请使用 RestTemplateCustomizer bean。所有此类 bean 都会自动注册到自动配置的 RestTemplateBuilder 中,并应用于使用它构建的任何模板。

以下示例显示了一个自定义器,它配置除 192.168.0.5 之外的所有主机都使用代理

  • Java

  • Kotlin

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.web.client.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.web.client.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。这样做将替换自动配置的 builder。如果你希望将任何 RestTemplateCustomizer bean 应用于你的自定义 builder(就像自动配置所做的那样),请使用 RestTemplateBuilderConfigurer 对其进行配置。以下示例展示了一个 RestTemplateBuilder,它与 Spring Boot 自动配置所做的匹配,只是还指定了自定义的连接和读取超时时间

  • Java

  • Kotlin

import java.time.Duration;

import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.boot.web.client.RestTemplateBuilder;
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.autoconfigure.web.client.RestTemplateBuilderConfigurer
import org.springframework.boot.web.client.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))
	}

}

最极端的(并且很少使用)选项是不使用 configurer 创建自己的 RestTemplateBuilder bean。除了替换自动配置的 builder 之外,这还会阻止使用任何 RestTemplateCustomizer bean。

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

RestTemplate SSL 支持

如果你需要在 RestTemplate 上进行自定义 SSL 配置,可以将 SSL 捆绑包应用于 RestTemplateBuilder,如本例所示

  • Java

  • Kotlin

import org.springframework.boot.docs.io.restclient.resttemplate.Details;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.client.RestTemplateBuilder;
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.web.client.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 会根据应用程序 classpath 中可用的库自动检测与 RestClientRestTemplate 一起使用的 HTTP 客户端。按照偏好顺序,支持以下客户端

  1. Apache HttpClient

  2. Jetty HttpClient

  3. Reactor Netty HttpClient

  4. JDK 客户端 (java.net.http.HttpClient)

  5. 简单 JDK 客户端 (java.net.HttpURLConnection)

如果 classpath 中有多个客户端可用,并且没有提供全局配置,将使用优先级最高的客户端。

全局 HTTP 客户端配置

如果自动检测到的 HTTP 客户端不符合你的需求,可以使用 spring.http.client.factory 属性选择特定的工厂。例如,如果你的 classpath 中有 Apache HttpClient,但你更喜欢 Jetty 的 HttpClient,可以添加以下内容

  • Properties

  • YAML

spring.http.client.factory=jetty
spring:
  http:
    client:
      factory: jetty

你还可以设置属性来更改将应用于所有客户端的默认值。例如,你可能想更改超时时间和是否跟随重定向

  • Properties

  • YAML

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

对于更复杂的自定义,你可以声明自己的 ClientHttpRequestFactoryBuilder bean,这将导致自动配置回退。当你需要自定义底层 HTTP 库的一些内部细节时,这会很有用。

例如,以下内容将使用配置了特定 ProxySelector 的 JDK 客户端

  • Java

  • Kotlin

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
import java.net.http.HttpClient

@Configuration(proxyBeanMethods = false)
class MyClientHttpConfiguration {

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

}