调用 REST 服务

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

WebClient

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

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

Spring Boot 为您创建并预先配置了一个原型 WebClient.Builder bean。强烈建议您将其注入到您的组件中,并使用它来创建 WebClient 实例。Spring Boot 配置该构建器以共享 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 运行时

Spring Boot 会根据应用程序类路径中可用的库自动检测要使用哪个 ClientHttpConnector 来驱动 WebClient。以下客户端按优先级排序:

  1. Reactor Netty

  2. Jetty RS 客户端

  3. Apache HttpClient

  4. JDK HttpClient

如果类路径中存在多个客户端,则将使用最优先的客户端。

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

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

如果您希望覆盖客户端的该选择,您可以定义自己的 ClientHttpConnector bean,并完全控制客户端配置。

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

WebClient 定制

根据您希望自定义应用的范围,有三种主要方法可以自定义WebClient

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

为了对所有WebClient.Builder实例进行应用程序范围的增量自定义,您可以声明WebClientCustomizer bean,并在注入点本地更改WebClient.Builder

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

WebClient SSL 支持

如果您需要在WebClient使用的ClientHttpConnector上进行自定义 SSL 配置,您可以注入一个WebClientSsl实例,该实例可以与构建器的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 使用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实例是有状态的:对构建器的任何更改都会反映在随后使用它创建的所有客户端中。如果您想使用同一个构建器创建多个客户端,您也可以考虑使用RestClient.Builder other = builder.clone();克隆构建器。

为了对所有RestClient.Builder实例进行应用程序范围的增量自定义,您可以声明RestClientCustomizer bean,并在注入点本地更改RestClient.Builder

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

RestClient SSL 支持

如果您需要在 RestClient 使用的 ClientHttpRequestFactory 上进行自定义 SSL 配置,您可以注入一个 RestClientSsl 实例,该实例可与构建器的 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 类与 ClientHttpRequestFactories 一起使用。

  • Java

  • Kotlin

import java.time.Duration;

import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
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.DEFAULTS
			.withReadTimeout(Duration.ofMinutes(2))
			.withSslBundle(sslBundles.getBundle("mybundle"));
		ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(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.ssl.SslBundles
import org.springframework.boot.web.client.ClientHttpRequestFactories
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings
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 = ClientHttpRequestFactories.get(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 实例,因此自定义只会影响此构建器的使用。

要进行应用程序范围的增量自定义,请使用 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。这样做将替换自动配置的构建器。如果您希望将任何RestTemplateCustomizer bean 应用于您的自定义构建器,就像自动配置所做的那样,请使用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())
			.setConnectTimeout(Duration.ofSeconds(5))
			.setReadTimeout(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()).setConnectTimeout(Duration.ofSeconds(5))
			.setReadTimeout(Duration.ofSeconds(2))
	}

}

最极端(也是最少使用)的选择是创建自己的RestTemplateBuilder bean,而不使用配置器。除了替换自动配置的构建器之外,这还会阻止使用任何RestTemplateCustomizer bean。

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.setSslBundle(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.setSslBundle(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 客户端。按优先级排序,支持以下客户端:

  1. Apache HttpClient

  2. Jetty HttpClient

  3. OkHttp(已弃用)

  4. 简单 JDK 客户端(HttpURLConnection

如果类路径中存在多个客户端,则将使用最优先的客户端。