呼叫 REST 服務

Spring Boot 提供了多種便捷的方式來呼叫遠端 REST 服務。如果您正在開發非阻塞反應式應用程式,並且您正在使用 Spring WebFlux,那麼您可以使用 WebClient。如果您偏好阻塞式 API,則可以使用 RestClientRestTemplate

WebClient

如果您的類別路徑上有 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 執行期

Spring Boot 將自動偵測要使用哪個 ClientHttpConnector 來驅動 WebClient,具體取決於應用程式類別路徑上可用的程式庫。依照偏好順序,支援以下客戶端

  1. Reactor Netty

  2. Jetty RS client

  3. Apache HttpClient

  4. JDK HttpClient

如果類別路徑上有多個客戶端可用,則將使用最優先的客戶端。

spring-boot-starter-webflux starter 預設依賴於 io.projectreactor.netty:reactor-netty,它同時帶來了伺服器和客戶端實作。如果您選擇使用 Jetty 作為反應式伺服器,則應新增對 Jetty Reactive HTTP client 程式庫 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 支援

如果您需要在 WebClient 使用的 ClientHttpConnector 上進行自訂 SSL 組態,您可以注入一個 WebClientSsl 實例,該實例可以與 builder 的 apply 方法一起使用。

WebClientSsl 介面提供了對您在 application.propertiesapplication.yaml 檔案中定義的任何 SSL bundles 的存取權。

以下程式碼顯示了一個典型的範例

  • 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 來配置該 builder。

以下程式碼顯示了一個典型的範例

  • 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

RestClient SSL 支援

如果您需要在 RestClient 使用的 ClientHttpRequestFactory 上進行自訂 SSL 組態,您可以注入一個 RestClientSsl 實例,該實例可以與 builder 的 apply 方法一起使用。

RestClientSsl 介面提供了對您在 application.propertiesapplication.yaml 檔案中定義的任何 SSL bundles 的存取權。

以下程式碼顯示了一個典型的範例

  • 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 bundle 之外還需要應用其他客製化,則可以將 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 實例,因此客製化僅影響 builder 的此用法。

為了進行應用程式範圍的、附加的客製化,請使用 RestTemplateCustomizer bean。所有此類 bean 都會自動註冊到自動配置的 RestTemplateBuilder,並應用於使用它建置的任何模板。

以下範例顯示了一個 customizer,它配置了除 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())
			.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))
	}

}

最極端(且很少使用)的選項是在不使用 configurer 的情況下建立您自己的 RestTemplateBuilder bean。除了取代自動配置的 builder 之外,這也阻止了任何 RestTemplateCustomizer bean 被使用。

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.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. Simple JDK client (HttpURLConnection)

如果類別路徑上有多個客戶端可用,則將使用最優先的客戶端。