使用 RestTemplate 進行第三方Rest服務呼叫

碼農小胖哥發表於2020-04-17

1. 前言

RestTemplateSpring 提供的一個呼叫 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 容器。 我們可以這樣初始化它:

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();
    }
}

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

2.1 RestTemplate 底層

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

3. 常用方法場景舉例

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

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

3.1 {get|post}ForEntity

Get 請求後將響應對映為 ResponseEntity<T> 響應物件,一個響應體的包裝物件。我們使用下列程式碼來隨機請求 5 條漂亮小姐姐的照片,你可以列印進行檢視:

    @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 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);

或者使用 Map<String,Object>

        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
        HashMap<String, Object> 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);

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

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

  • 請求體物件,可使用實體 VO、MultiValueMap
  • 請求頭物件, org.springframework.http.HttpHeaders
 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, Object> uriParams = new HashMap<>();
        uriParams.put("page", 0);
        uriParams.put("count", 5);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class, uriParams);

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

3.2 {get|post}ForObject

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

        String url = "https://api.apiopen.top/getImages?page={page}&count={count}";
        HashMap<String, Object> uriParams = new HashMap<>();
        uriParams.put("page", 0);
        uriParams.put("count", 5);
        String forObject = restTemplate.getForObject(url, String.class, uriParams);
        System.out.println("forObject = " + forObject);

3.3 headForHeaders

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

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

結果為:

[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"]

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 是一個很有用的請求協調器,遮蔽了呼叫服務的複雜度而又不失靈活。但是值得注意的是它正在退出歷史舞臺。再牛逼的程式設計師也有轉行的那一天不是嗎?

關注公眾號:Felordcn 獲取更多資訊

個人部落格:https://felord.cn

相關文章