聊一聊 RestTemplate

glmapper發表於2018-10-20

最近這段時間用了下 RestTemplate 這個類,抽點時間總結下一些東西,希望對大家有所幫助。

從 3.0 版本開始,Spring 提供了 RestTemplate 作為用於訪問 Rest 服務的客戶端,RestTemplate 提供了多種便捷訪問遠端 Http 服務的方法,能夠大大提高客戶端的編寫效率。

本篇文章將從 RestTemplate 提供的 API 入手,先來了解下 RestTemplate 的具體使用,然後再對其中涉及到的幾個核心類進行分析,最後再來分析下 RestTemplate 執行的整個流程,篇幅比較長,建議先碼為快!

核心 API

在平時的使用中,我們通常都是使用包裝好的getForObject/getForEntity,postForObject/postForEntity/postForLocation,put以及delete。

get 請求處理

getForEntity方法的返回值是一個ResponseEntity,ResponseEntity是Spring對HTTP請求響應的封裝,包括了幾個重要的元素,如響應碼、contentType、contentLength、響應訊息體等。

  • url:呼叫的服務的地址
  • responseType:返回的body型別
  • uriVariables:有兩種形式:
    • 可以用一個數字做佔位符,最後是一個可變長度的引數,來一一替換前面的佔位符
      聊一聊 RestTemplate
    • 也可以前面使用name={name}這種形式,最後一個引數是一個map,map的key即為前邊佔位符的名字,map的value為引數值
      聊一聊 RestTemplate

responseType 測試案例

定義的一個controller資源:

聊一聊 RestTemplate
這裡分別使用不同的 responseType 進行測試:

聊一聊 RestTemplate

結果:

getForEntity(responseType=Map.class):{glmapper=hello glmapper}
getForEntity(responseType=String.class):{"glmapper":"hello glmapper"}
複製程式碼

uriVariables 測試案例

先來看下非map方式的,兩個controller,兩種不同方式的引數獲取(本質上是一樣的)

聊一聊 RestTemplate

  • 使用佔位符的方式:

聊一聊 RestTemplate

  • 使用 map 的方式:

聊一聊 RestTemplate

getForObject

getForObject 函式實際上是對 getForEntity 函式的進一步封裝,如果只關注返回的訊息體的內容,對其他資訊都不關注,那麼就可以使用 getForObject。

聊一聊 RestTemplate

這裡呼叫就比getForEntity要簡單一點了,可以直接拿到物件:

聊一聊 RestTemplate

getForObject 的幾個過載方法和 getForEntity 基本是一樣的。

post 請求處理

在RestTemplate中,POST請求可以通過如下三個方法來發起:postForEntity,postForObject,postForLocation。

postForEntity 案例

聊一聊 RestTemplate
呼叫獲取:

聊一聊 RestTemplate

postForEntity(URI url, @Nullable Object request, Class<T> responseType)
複製程式碼
  • 方法的第一參數列示要呼叫的服務的地址
  • 方法的第二個參數列示上傳的引數
  • 方法的第三個參數列示返回的訊息體的資料型別

postForObject 案例

和 getForObject 相對應,只關注返回的訊息體。

聊一聊 RestTemplate

postForLocation 案例

postForLocation也是提交新資源,提交成功之後,返回新資源的URI,postForLocation的引數和前面兩種的引數基本一致,只不過該方法的返回值為Uri,這個只需要服務提供者返回一個Uri即可,該Uri表示新資源的位置。

聊一聊 RestTemplate

這裡有點坑,我們需要把這個uri新增到response的header中,不然後面拿到的是null。

聊一聊 RestTemplate

exchange

exchange 方法和上述這些方法差別在於需要多一個請求型別的引數:

聊一聊 RestTemplate

AsyncRestTemplate 非同步客戶端

RestTemplate的非同步實現方式。所涉及到的API和RestTemplate基本一致。區別在於RestTemplate直接返回結果,而AsyncRestTemplate返回的是ListenableFuture。

聊一聊 RestTemplate

RestTemplate 攔截器

Spring提供了ClientHttpRequestInterceptor和AsyncClientHttpRequestInterceptor兩個介面,分別可以對RestTemplate和AsyncRestTemplate發起的請求進行攔截,並在其被髮送至服務端之前修改請求或是增強相應的資訊。

  • ClientHttpRequestInterceptor 攔截 RestTemplate

    聊一聊 RestTemplate

  • AsyncClientHttpRequestInterceptor 攔截AsyncRestTemplate

    聊一聊 RestTemplate

設定攔截器就是通過提供的 setInterceptors 設定即可:

聊一聊 RestTemplate

自定義 ResponseErrorHandler

ResponseErrorHandler 介面定義了當response發生錯誤時需要進行的操作。這裡我們自定義一個CustomResponseErrorHandler,當返回的code不是200時,就表示執行出錯了。

聊一聊 RestTemplate

設定 ResponseErrorHandler:

聊一聊 RestTemplate

執行結果:

聊一聊 RestTemplate

處理流程

下面來梳理下 RestTemplate 中請求處理的流程。下圖中 XXXX 表示我們呼叫的 API 方法。大體流程就是:api 內部做一些請求相關的處理封裝,然後交給 execute 方法執行,最後真正處理則是在 doExecute 方法中完成。

聊一聊 RestTemplate

下面以 getForEntity 方法的執行過程來分析:

聊一聊 RestTemplate

getForEntity 方法:

  • 基於給定響應型別,返回一個請求回撥實現,準備請求。
  • 基於給定響應型別,返回 ResponseEntity 的響應提取器。

聊一聊 RestTemplate
execute 方法:

  • 這個方法裡面是對url進行urlencode編碼處理的,統一轉為URL。這裡我們也可以手動把引數進行網路編碼。

聊一聊 RestTemplate
doExecute是請求真正處理的方法,這裡來重點看下這個方法的執行過程:

  • createRequest
  • doWithRequest
  • execute
  • handleResponse

1、createRequest

這個方法的作用就是建立一個 ClientHttpRequest 物件。RestTemplate整合了 HttpAccessor這個抽象類,建立ClientHttpRequest的過程就是在其父類HttpAccessor中通過預設的 ClientHttpRequestFactory 實現類 SimpleClientHttpRequestFactory 完成具體的請求建立。

聊一聊 RestTemplate

  • 1、建立 java.net.HttpURLConnection 物件

  • 2、設定 connection,包括 connectTimeout、setDoInput 等。

  • 3、bufferRequestBody 用於標誌是否使用快取流的形式,預設是 true。缺點是當傳送大量資料時,比如 put/post,存在記憶體消耗嚴重。該值可以通過 SimpleClientHttpRequestFactory#setBufferRequestBody來修改。

不同版本的變更還是比較大的,大家在閱讀原始碼時,還是從最新的程式碼來看。

2、doWithRequest

RequestCallback 封裝了請求體和請求頭物件。這裡會遍歷所有的 HttpMessageConverter,解析成所有支援的MediaType,放在allSupportedMediaTypes中。

request.getHeaders().setAccept(allSupportedMediaTypes);
複製程式碼

RestTemplate中對應了兩個內部類的實現:

  • AcceptHeaderRequestCallback.doWithRequest的處理。 傳送請求時,Http頭部需要設定Accept欄位,該欄位表明了傳送請求的這方接受的媒體型別(訊息格式),也是響應端要返回的資訊的媒體型別(訊息格式)。 根據postForEntity方法的第三個引數responseType,程式將選擇適合的解析器XXXConverter,並依據該解析器找出所有支援的媒體型別。

  • HttpEntityRequestCallback.doWithRequest的處理。 如果是POST請求並且訊息體存在時,除了設定Accept欄位,還可能需要設定Content-Type欄位,該欄位表明了所傳送請求的媒體型別(訊息格式),也是響應端接受的媒體型別(訊息格式)。 根據postForEntity方法的第二個引數request,程式將選擇適合的解析器XXXConverter,將請求訊息寫入輸出流。

3、execute

這裡會把請求頭/體封裝到connect,然後傳送請求。跟蹤 execute 方法執行,定位到SimpleBufferingClientHttpRequest#executeInternal方法:

聊一聊 RestTemplate
這裡是使用例項 SimpleBufferingClientHttpRequest 封裝請求體和請求頭。從程式碼中可以看到:

  • delete 時通過前面設定的 DoOutput引數和是否可以設定輸出流來判斷是否需要傳送請求體如果是 delete 請求,那麼很明顯 DoOutput = false,不會有封裝請求體的過程,即不執行FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream())。

4、handleResponse

最後就是 response 的解析了,從程式碼來看,主要還是 Error 的解析。這裡的ErrorHandler我們前面也提到,可以通過實現 ResponseErrorHandler 來自定義 異常處理。

聊一聊 RestTemplate

小結

本篇先介紹了RestTemplate的API使用,挑了幾個介紹了下,更多使用細節還是要針對不同的場景來決定。接著對攔截器,非同步RestTemplate以及錯誤處理器做了簡單的介紹並給出了案例。最後分析了下RestTemplate的執行流程,篇幅原因執行流程部分只是大概捋了捋,其中還是很多細節有時間再補充,這部分主要就是看底層是如何通訊的,已經請求引數的傳遞等。

相關文章