呼叫 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) }
	}

}