调用 REST 服务
Spring Boot 提供了多种便捷的方式来调用远程 REST 服务。如果你正在开发一个非阻塞的响应式应用程序,并且你使用的是 Spring WebFlux,那么你可以使用 WebClient。如果你更倾向于使用阻塞式 API,那么你可以使用 RestClient 或 RestTemplate。
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,具体取决于应用程序类路径上可用的库。按照优先级顺序,支持以下客户端:
-
Reactor Netty
-
Jetty RS 客户端
-
Apache HttpClient
-
JDK HttpClient
如果在类路径上有多个客户端可用,将使用最优先的客户端。
默认情况下,spring-boot-starter-webflux
启动器依赖于 io.projectreactor.netty:reactor-netty
,它同时带来了服务器和客户端的实现。如果你选择使用 Jetty 作为响应式服务器,你应该添加 Jetty 响应式 HTTP 客户端库 org.eclipse.jetty:jetty-reactive-httpclient
的依赖。在服务器和客户端使用相同的技术有其优势,因为它会自动在客户端和服务器之间共享 HTTP 资源。
开发者可以通过提供一个自定义的 ReactorResourceFactory 或 JettyResourceFactory 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.properties
或 application.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。
你也可以更改 全局 HTTP 客户端配置。
RestClient SSL 支持
如果你需要在 RestClient 使用的 ClientHttpRequestFactory 上进行自定义 SSL 配置,你可以注入一个 RestClientSsl 实例,该实例可以与构建器的 apply
方法一起使用。
RestClientSsl 接口提供了对你在 application.properties
或 application.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 实例,因此自定义只会影响该构建器的使用。
要进行应用程序范围内的、可叠加的自定义,可以使用 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 来配置它。以下示例展示了一个与 Spring Boot 自动配置相匹配的 RestTemplateBuilder,只是额外指定了自定义的连接和读取超时时间:
- 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))
}
}
最极端(且很少使用)的选项是在不使用配置器的情况下创建你自己的 RestTemplateBuilder Bean。除了替换自动配置的构建器外,这还会阻止任何 RestTemplateCustomizer Bean 被使用。
你也可以更改 全局 HTTP 客户端配置。
RestTemplate SSL 支持
如果你需要在 RestTemplate 上自定义 SSL 配置,你可以像这个例子中所示,将一个 SSL bundle 应用到 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 会根据应用程序类路径上可用的库,自动检测与 RestClient 和 RestTemplate 一起使用的 HTTP 客户端。按照优先顺序,支持以下客户端:
-
Apache HttpClient
-
Jetty HttpClient
-
Reactor Netty HttpClient
-
JDK 客户端(
java.net.http.HttpClient
) -
简单 JDK 客户端(
java.net.HttpURLConnection
)
如果在类路径上存在多个客户端,并且没有提供全局配置,那么将使用最优先的客户端。
全局 HTTP 客户端配置
如果自动检测的 HTTP 客户端无法满足你的需求,你可以使用 spring.http.client.factory
属性来选择一个特定的工厂。例如,如果你的类路径中有 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) }
}
}