使用 RestTemplate 進行第三方Rest服務調用

使用 RestTemplate 進行第三方Rest服務調用

1. 前言

RestTemplate 是 Spring 提供的一個調用 Restful 服務的抽象層,它簡化的同 Restful 服務的通信方式,隱藏了不必要的一些細節,讓我們更加優雅地在應用中調用 Restful 服務 。但是在 Spring 5.0 以後RestTemplate處於維護模式,不再進行新特性的開發,僅僅進行一些日常維護。Spring 建議我們使用同時支持同步、異步和 Stream 的另一個 API —— WebClient 。但是在 Spring MVC 下目前我們還沒有更好的選擇。

2. RestTemplate 的使用場景

我們在項目中經常要使用第三方的 Rest API 服務,比如短信、快遞查詢、天氣預報等等。這些第三方只要提供了 Rest Api

,你都可以使用 RestTemplate 來調用它們。

3. 初始化 RestTemplate

只要你的項目使用了 Spring MVC 就已經集成了RestTemplate 。但是通常情況下該類不會自動被注入 Spring IoC容器,因為很多 Rest API 都具有特殊性,為了更加靈活的進行定製,其構建類 RestTemplateBuilder被自動注入了 Spring IoC 容器。 我們可以這樣初始化它:

<code>package cn.felord.rest.webclient;

import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/**
* @author felord.cn
* @since 14:58
**/
@Component
public class SomeWeb {

private final RestTemplateBuilder restTemplateBuilder;

public SomeWeb(RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}

public RestTemplate restTemplate() {
// 通過 builder 定製
return restTemplateBuilder.requestFactory(OkHttp3ClientHttpRequestFactory::new).
build();
}
}/<code>

最佳實踐:針對每一個第三方服務儘量定製對應的 RestTemplate,儘量不公用,除非這些第三方的流程完全一致。

2.1 RestTemplate 底層

默認情況下,RestTemplate 使用 java.net.HttpURLConnection 作為實現,一但使用它時有異常響應狀態(比如 401),就會引發異常,因此我們一般不使用它。我們可以切換到 NettyApache HttpComponentsokHttp 默認實現的客戶端庫,參考 2 中的 requestFactory(ClientHttpRequestFactory factory) 接入方法,也可以自行實現 ClientHttpRequestFactory 對接其它第三方庫進行接入。這裡我使用 okHttp 。你可以定製這些第三方庫提供的特性豐富你的 RestTemplate,比如設置請求超時。

3. 常用方法場景舉例

RestTemplate 支持所有 Restful 風格方法,你可以根據需要進行選擇,這裡我們只介紹一些常用的方法。所有方法都支持URI 模板和 URI 參數,支持下面這種寫法:

<code># 類似 spring mvc 中的 @PathVariable
https://api.apiopen.top/{method}/<code>

3.1 {get|post}ForEntity

Get 請求後將響應映射為 ResponseEntity 響應對象,一個響應體的包裝對象。我們使用下列代碼來隨機請求 5 條漂亮小姐姐的照片,你可以打印進行查看:

<code>    @Autowired
RestTemplate restTemplate;

void contextLoads() {
String url = "https://api.apiopen.top/getImages?page=0&count=5";
ResponseEntity<string> responseEntity = restTemplate
.getForEntity(url,String.class);
String body = responseEntity.getBody();
System.out.println("body = " + body);
}/<string>/<code>

上面的方法改為按順序的可變參數:

<code>        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
ResponseEntity<string> responseEntity = restTemplate
.getForEntity(url,String.class,0,5);
String body = responseEntity.getBody();
System.out.println("body = " + body);/<string>/<code>

或者使用 Map<string>:/<string>

<code>        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
HashMap<string> uriParams = new HashMap<>();
uriParams.put("page", 0);
uriParams.put("count", 5);
ResponseEntity<string> responseEntity = restTemplate
.getForEntity(url, String.class, uriParams);
String body = responseEntity.getBody();
System.out.println("body = " + body);/<string>/<string>/<code>

post 請求 額外會傳入一個可能為 null 的 VO 對象,或者 MultiValueMap 來攜帶請求體參數 ,它們最終會被封裝入

org.springframework.http.HttpEntity 對象,該對象可包含以下兩個部分:

  • 請求體對象,可使用實體 VO、MultiValueMap
  • 請求頭對象, org.springframework.http.HttpHeaders
<code> String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<uservo> httpEntity = new HttpEntity<>(new UserVO("userName"), headers);
HashMap<string> uriParams = new HashMap<>();
uriParams.put("page", 0);
uriParams.put("count", 5);
ResponseEntity<string> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class, uriParams);/<string>/<string>/<uservo>/<code>

以上是一個調用 Post 請求並攜帶請求體和請求頭的示例。

3.2 {get|post}ForObject

我們還可以將響應直接映射到 POJO, 當然你需要對響應結果的結構非常瞭解,建議先映射到 String 查看一下結構。我們給出一種示例,其他示例參考 3.1 :

<code>        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
HashMap<string> uriParams = new HashMap<>();
uriParams.put("page", 0);
uriParams.put("count", 5);

String forObject = restTemplate.getForObject(url, String.class, uriParams);
System.out.println("forObject = " + forObject);/<string>/<code>

3.3 headForHeaders

該方法用於獲取所有的 URI 模板聲明資源的 Header

<code>        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
HashMap<string> uriParams = new HashMap<>();
uriParams.put("page", 0);
uriParams.put("count", 5);
HttpHeaders httpHeaders = restTemplate.headForHeaders(url, uriParams);
System.out.println(httpHeaders);/<string>/<code>

結果為:

<code>[Access-Control-Allow-Headers:"Content-Type, x-requested-with, X-Custom-Header, Authorization", Access-Control-Allow-Methods:"POST, GET, OPTIONS, DELETE", Access-Control-Allow-Origin:"*", Access-Control-Max-Age:"3600", Cache-Control:"private", Content-Length:"608", Content-Type:"application/json;charset=UTF-8", Date:"Tue, 14 Apr 2020 15:25:19 GMT", Expires:"Thu, 01 Jan 1970 00:00:00 GMT"]/<code>

3.4 postForLocation

Post 操作不是返回完整的資源,而是返回新創建的資源 URI 。比如上傳文件返回資源的請求路徑。

3.5 put/delete

對應 put 請求 和 delete 請求,參考前面的 api。

3.6 optionsForAllow

該方法獲取該 URI 允許的所有請求方法比如 GET、POST、PUT、DELETE 中的一個或者幾個。

3.7 exchange

該方法是通用的請求方式,支持 GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE,當上面的方式不能滿足你可採用該方式定製,該方式提供了更加靈活的 API,比如你可以定製 GET 方法的請求頭,放入 Jwt Token等操作,這是getForObject 無法比擬的。

4. 總結

RestTemplate 是一個很有用的請求協調器,屏蔽了調用服務的複雜度而又不失靈活。但是值得注意的是它正在退出歷史舞臺。再牛逼的程序員也有轉行的那一天不是嗎?


分享到:


相關文章: