Spring6提供的四種遠(yuǎn)程接口調(diào)用神器!你知道那種?
1. 介紹
Spring 6是一個(gè)非常強(qiáng)大的框架,它提供了許多工具和接口來簡(jiǎn)化遠(yuǎn)程接口調(diào)用。其中,WebClient、RestTemplate、HTTP Interface和RestClient是四種方式。
WebClient是Spring 5中新引入的一個(gè)接口基于響應(yīng)式,它提供了一種更簡(jiǎn)單、更靈活的方式來調(diào)用遠(yuǎn)程接口。與RestTemplate相比,WebClient更加現(xiàn)代化,具有更好的性能和更低的內(nèi)存占用。
RestTemplate是Spring 3中引入的一個(gè)接口,它提供了一種更加簡(jiǎn)單、更加直觀的方式來調(diào)用遠(yuǎn)程接口。雖然WebClient是更現(xiàn)代化的選擇,但RestTemplate仍然是一種常用的遠(yuǎn)程接口調(diào)用方式。
HTTP Interface將 HTTP 服務(wù)定義為一個(gè) Java 接口,其中包含用于 HTTP 交換的注解方法。然后,你可以生成一個(gè)實(shí)現(xiàn)該接口并執(zhí)行交換的代理。這有助于簡(jiǎn)化 HTTP 遠(yuǎn)程訪問,因?yàn)檫h(yuǎn)程訪問通常需要使用一個(gè)門面來封裝使用底層 HTTP 客戶端的細(xì)節(jié)。
RestClient是一個(gè)同步 HTTP 客戶端,提供現(xiàn)代、流暢的 API。它為 HTTP 庫提供了一個(gè)抽象,可以方便地從 Java 對(duì)象轉(zhuǎn)換為 HTTP 請(qǐng)求,并從 HTTP 響應(yīng)創(chuàng)建對(duì)象。
下面分別介紹4個(gè)REST接口調(diào)用的詳細(xì)使用。
2. 遠(yuǎn)程接口調(diào)用
RestTemplate
RestTemplate 提供了比 HTTP 客戶端庫更高級(jí)別的 API。它使調(diào)用 REST 端點(diǎn)變得簡(jiǎn)單易行。它公開了以下幾組重載方法:
方法 | 描述 |
getForObject | 通過GET檢索數(shù)據(jù)。 |
getForEntity | 使用 GET 獲取響應(yīng)實(shí)體(即狀態(tài)、標(biāo)頭和正文)。 |
headForHeaders | 使用 HEAD 讀取資源的所有標(biāo)頭。 |
postForLocation | 使用 POST 創(chuàng)建新資源,并從響應(yīng)中返回位置標(biāo)頭。 |
postForObject | 使用 POST 創(chuàng)建一個(gè)新資源,并從響應(yīng)中返回描述。 |
postForEntity | 使用 POST 創(chuàng)建一個(gè)新資源,并從響應(yīng)中返回描述。 |
put | 使用 PUT 創(chuàng)建或更新資源。 |
patchForObject | 使用 PATCH 更新資源,并返回響應(yīng)中的描述。請(qǐng)注意,JDK HttpURLConnection 不支持 PATCH,但 Apache HttpComponents 和其他組件支持。 |
delete | 使用 DELETE 刪除指定 URI 上的資源。 |
optionsForAllow | 通過 ALLOW 讀取資源允許使用的 HTTP 方法。 |
exchange | 前述方法的更通用(更少意見)版本,可在需要時(shí)提供額外的靈活性。它接受一個(gè) RequestEntity(包括作為輸入的 HTTP 方法、URL、標(biāo)題和正文),并返回一個(gè) ResponseEntity。 這些方法允許使用參數(shù)化類型引用(ParameterizedTypeReference)而不是類(Class)來指定具有泛型的響應(yīng)類型。 |
execute | 執(zhí)行請(qǐng)求的最通用方式,可通過回調(diào)接口完全控制請(qǐng)求準(zhǔn)備和響應(yīng)提取。 |
- 初始化
默認(rèn)構(gòu)造函數(shù)使用 java.net.HttpURLConnection 來執(zhí)行請(qǐng)求。你可以通過 ClientHttpRequestFactory 的實(shí)現(xiàn)切換到不同的 HTTP 庫。目前,該程序還內(nèi)置了對(duì) Apache HttpComponents 和 OkHttp 的支持。示例:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每個(gè) ClientHttpRequestFactory 都會(huì)公開底層 HTTP 客戶端庫的特定配置選項(xiàng),例如,憑證、連接池和其他細(xì)節(jié)。
- URIs
許多 RestTemplate 方法都接受 URI 模板和 URI 模板變量,可以是字符串變量參數(shù),也可以是 Map<String,String>。
String result = restTemplate.getForObject(
"http://pack.com/users/{userId}", String.class, 666) ;
Map<String, ?>方式:
Map<String, Object> params = Collections.singletonMap("userId", 666);
String result = restTemplate.getForObject(
"http://pack.com/users/{userId}", String.class, params) ;
- Headers
可以使用 exchange() 方法指定請(qǐng)求頭,如下例所示:
String uriTemplate = "http://pack.com/users/{userId}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("x-api-token", "aabbcc")
.build() ;
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("x-version");
String body = response.getBody() ;
- body
如果你當(dāng)前CLASSPATH存在MappingJackson2HttpMessageConverter,那么你可以直接將請(qǐng)求結(jié)果映射為你所需要的結(jié)果對(duì)象,如下示例所示,將目標(biāo)接口返回值直接轉(zhuǎn)換為User對(duì)象。
User user = restTemplate.getForObject("http://pack.com/users/{userId}", User.class, 666);
默認(rèn)情況下,RestTemplate 會(huì)注冊(cè)所有內(nèi)置的消息轉(zhuǎn)換器,這決定于你當(dāng)前類路徑是否有相應(yīng)的轉(zhuǎn)換庫。你也可以顯式設(shè)置要使用的消息轉(zhuǎn)換器。默認(rèn)構(gòu)造函數(shù)如下:
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
// ...其它轉(zhuǎn)換器
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
// ...其它轉(zhuǎn)換器
this.uriTemplateHandler = initUriTemplateHandler();
}
注意:RestTemplate 目前處于維護(hù)模式,只接受小改動(dòng)和錯(cuò)誤請(qǐng)求。請(qǐng)考慮改用 WebClient。
WebClient
WebClient 是執(zhí)行 HTTP 請(qǐng)求的非阻塞、反應(yīng)式客戶端。它在 5.0 中引入,提供了 RestTemplate 的替代方案,支持同步、異步和流場(chǎng)景。
WebClient 支持以下功能:
- 非阻塞 I/O。
- 反應(yīng)流反向壓力
- 用更少的硬件資源實(shí)現(xiàn)高并發(fā)。
- 利用 Java 8 lambdas 的函數(shù)式流暢應(yīng)用程序接口。
- 同步和異步交互。
- 服務(wù)器上的數(shù)據(jù)流或服務(wù)器下的數(shù)據(jù)流。
示例:
Mono<Person> result = client.get()
.uri("/users/{userId}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(User.class);
HTTP Interface
Spring Framework 可讓你將 HTTP 服務(wù)定義為一個(gè) Java 接口,其中包含用于 HTTP 交換的注解方法。然后,你可以生成一個(gè)實(shí)現(xiàn)該接口并執(zhí)行交換的代理。這有助于簡(jiǎn)化 HTTP 遠(yuǎn)程訪問,因?yàn)檫h(yuǎn)程訪問通常需要使用一個(gè)門面來封裝使用底層 HTTP 客戶端的細(xì)節(jié)。
首先,聲明一個(gè)帶有 @HttpExchange 方法的接口:
@HttpExchange(url = "/demos")
public interface DemoInterface {
@PostExchange("/format3/{id}")
Users queryUser(@PathVariable Long id);
}
創(chuàng)建一個(gè)代理,執(zhí)行所聲明的 HTTP exchanges:
@Service
public class DemoService {
private final DemoInterface demoInterface ;
public DemoService() {
// 基于響應(yīng)式調(diào)用;你當(dāng)前的環(huán)境需要引入webflux
WebClient client = WebClient.builder().baseUrl("http://localhost:8088/").build() ;
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build() ;
this.demoInterface = factory.createClient(DemoInterface.class) ;
}
public Users queryUser(Long id) {
return this.demoInterface.queryUser(id) ;
}
}
測(cè)試接口
@Resource
private DemoService demoService ;
@GetMapping("/{id}")
public Users getUser(@PathVariable("id") Long id) {
return this.demoService.queryUser(id) ;
}
執(zhí)行結(jié)果
圖片
支持的方法參數(shù)
方法參數(shù) | 說明 |
URI | 動(dòng)態(tài)設(shè)置請(qǐng)求的 URL,覆蓋注解的 url 屬性。 |
HttpMethod | 動(dòng)態(tài)設(shè)置請(qǐng)求的 HTTP 方法,覆蓋注解的方法屬性 |
@RequestHeader | 添加一個(gè)或多個(gè)請(qǐng)求標(biāo)頭。參數(shù)可以是包含多個(gè)標(biāo)頭的 Map<String, ?> 或 MultiValueMap<String, ?>、值集合<?> 或單個(gè)值。 |
@PathVariable | 添加一個(gè)變量,用于擴(kuò)展請(qǐng)求 URL 中的占位符。參數(shù)可以是包含多個(gè)變量的 Map<String, ?> 或單個(gè)值。 |
@RequestBody | 提供請(qǐng)求的正文,既可以是要序列化的對(duì)象,也可以是 Reactive Streams Publisher(如 Mono、Flux 或通過配置的 ReactiveAdapterRegistry 支持的任何其他異步類型)。 |
@RequestParam | 添加一個(gè)或多個(gè)請(qǐng)求參數(shù)。參數(shù)可以是包含多個(gè)參數(shù)的 Map<String, ?> 或 MultiValueMap<String, ?>、數(shù)值集合<?> 或單個(gè)數(shù)值。 當(dāng) "content-type"設(shè)置為 "application/x-www-form-urlencoded "時(shí),請(qǐng)求參數(shù)將在請(qǐng)求正文中編碼。否則,它們將作為 URL 查詢參數(shù)添加。 |
@RequestPart | 添加一個(gè)請(qǐng)求部分,它可以是字符串(表單字段)、資源(文件部分)、對(duì)象(要編碼的實(shí)體,如 JSON)、HttpEntity(部分內(nèi)容和標(biāo)頭)、Spring 部分或上述任何部分的 Reactive Streams 發(fā)布器。 |
@CookieValue | 添加一個(gè)或多個(gè) cookie。參數(shù)可以是包含多個(gè) cookie 的 Map<String, ?> 或 MultiValueMap<String, ?>、值集合<?> 或單個(gè)值。 |
支持的返回值
返回值 | 說明 |
| 執(zhí)行給定的請(qǐng)求,并發(fā)布響應(yīng)內(nèi)容(如果有)。 |
| 執(zhí)行給定的請(qǐng)求,釋放響應(yīng)內(nèi)容(如果有),并返回響應(yīng)標(biāo)頭。 |
| 執(zhí)行給定的請(qǐng)求,并根據(jù)聲明的返回類型對(duì)響應(yīng)內(nèi)容進(jìn)行解碼。 |
| 執(zhí)行給定的請(qǐng)求,并將響應(yīng)內(nèi)容解碼為已聲明元素類型的數(shù)據(jù)流。 |
| 執(zhí)行給定的請(qǐng)求,釋放響應(yīng)內(nèi)容(如果有),并返回一個(gè)包含狀態(tài)和標(biāo)頭的 ResponseEntity。 |
| 執(zhí)行給定的請(qǐng)求,按照聲明的返回類型解碼響應(yīng)內(nèi)容,并返回一個(gè)包含狀態(tài)、標(biāo)頭和解碼后正文的 ResponseEntity。 |
Mono<ResponseEntity<Flux<T>> | 執(zhí)行給定的請(qǐng)求,將響應(yīng)內(nèi)容解碼為已聲明元素類型的數(shù)據(jù)流,并返回一個(gè)包含狀態(tài)、標(biāo)頭和解碼后的響應(yīng)正文數(shù)據(jù)流的 ResponseEntity。 |
異常處理
默認(rèn)情況下,WebClient為4xx和5xx HTTP狀態(tài)代碼引發(fā)WebClientResponseException。要自定義此項(xiàng),可以注冊(cè)響應(yīng)狀態(tài)處理程序,該處理程序應(yīng)用于通過客戶端執(zhí)行的所有響應(yīng):
WebClient client = WebClient.builder()
// 狀態(tài)碼為4xx或5xx
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new RuntimeException(resp.statusCode().toString() + "請(qǐng)求錯(cuò)誤")))
.baseUrl("http://localhost:8088/").build() ;
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(client)).build() ;
RestClient
該接口是在Spring6.1.1版本中才有的,是一個(gè)同步 HTTP 客戶端,提供現(xiàn)代、流暢的 API。
創(chuàng)建RestClientRestClient 是通過靜態(tài)創(chuàng)建方法之一創(chuàng)建的。你還可以使用 builder 獲取帶有更多選項(xiàng)的生成器,例如指定要使用的 HTTP 庫和要使用的消息轉(zhuǎn)換器,設(shè)置默認(rèn) URI、默認(rèn)路徑變量、默認(rèn)請(qǐng)求頭,或注冊(cè)攔截器和初始化器。
創(chuàng)建(或構(gòu)建)后,RestClient 可由多個(gè)線程安全使用。
// 默認(rèn)通過靜態(tài)方法創(chuàng)建
RestClient restClient = RestClient.create();
// 自定義方式
RestClient restClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
// 自定義消息轉(zhuǎn)換器
// .messageConverters(converters -> converters.add(new PackCustomMessageConverter()))
.baseUrl("http://localhost:8088")
.defaultUriVariables(Map.of("id", "888"))
.defaultHeader("x-api-token", "aabbcc")
.requestInterceptor(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
System.out.println("我是攔截器") ;
return execution.execute(request, body) ;
}
})
.requestInitializer(new ClientHttpRequestInitializer() {
@Override
public void initialize(ClientHttpRequest request) {
System.out.println("我是初始化器") ;
request.getHeaders().add("x-version", "1.0.0") ;
}
})
.build() ;
調(diào)用及處理返回值
Users users = customClient.get()
.uri("/demos/users/{id}")
.retrieve()
.body(Users.class) ;
post+body請(qǐng)求方式
Users user = new Users();
ResponseEntity<Void> response = restClient.post()
.uri("/demos/users")
.contentType(APPLICATION_JSON)
.body(user)
.retrieve()
.toBodilessEntity() ;
錯(cuò)誤處理默認(rèn)情況下,當(dāng)返回狀態(tài)代碼為 4xx 或 5xx 的響應(yīng)時(shí),RestClient 會(huì)拋出 RestClientException 的子類??梢允褂?onStatus.RestClientException 命令重寫該行為。
Users users = customClient.get()
.uri("/demos/users/{id}")
.retrieve()
// 處理返回狀態(tài)碼為:4xx和5xx
.onStatus(HttpStatusCode::isError, (request, response) -> {
throw new RuntimeException(response.getStatusCode().toString() + "請(qǐng)求錯(cuò)誤") ;
})
.body(Users.class) ;
總結(jié):實(shí)現(xiàn)遠(yuǎn)程接口調(diào)用方面的強(qiáng)大功能。無論是使用WebClient、RestTemplate、HTTP Interface還是直接使用RestClient,Spring都提供了豐富的工具和接口來簡(jiǎn)化開發(fā)者的操作。這些工具和接口不僅具有高性能、低內(nèi)存占用的優(yōu)點(diǎn),而且提供了良好的可擴(kuò)展性和靈活性,使得開發(fā)者可以根據(jù)實(shí)際需求進(jìn)行定制化開發(fā)。