聊一聊 RestTemplate

最近這段時間用了下 RestTemplate 這個類,抽點時間總結下一些東西,但願對你們有所幫助。java

從 3.0 版本開始,Spring 提供了 RestTemplate 做爲用於訪問 Rest 服務的客戶端,RestTemplate 提供了多種便捷訪問遠程 Http 服務的方法,可以大大提升客戶端的編寫效率。api

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

核心 API

在平時的使用中,咱們一般都是使用包裝好的getForObject/getForEntity,postForObject/postForEntity/postForLocation,put以及delete。bash

get 請求處理

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

  • url:調用的服務的地址
  • responseType:返回的body類型
  • uriVariables:有兩種形式:
    • 能夠用一個數字作佔位符,最後是一個可變長度的參數,來一一替換前面的佔位符
    • 也能夠前面使用name={name}這種形式,最後一個參數是一個map,map的key即爲前邊佔位符的名字,map的value爲參數值

responseType 測試案例

定義的一個controller資源:app

這裏分別使用不一樣的 responseType 進行測試:

結果:異步

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

uriVariables 測試案例

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

  • 使用佔位符的方式:

  • 使用 map 的方式:

getForObject

getForObject 函數其實是對 getForEntity 函數的進一步封裝,若是隻關注返回的消息體的內容,對其餘信息都不關注,那麼就可使用 getForObject。post

這裏調用就比getForEntity要簡單一點了,能夠直接拿到對象:測試

getForObject 的幾個重載方法和 getForEntity 基本是同樣的。

post 請求處理

在RestTemplate中,POST請求能夠經過以下三個方法來發起:postForEntity,postForObject,postForLocation。

postForEntity 案例

調用獲取:

postForEntity(URI url, @Nullable Object request, Class<T> responseType)
複製代碼
  • 方法的第一參數表示要調用的服務的地址
  • 方法的第二個參數表示上傳的參數
  • 方法的第三個參數表示返回的消息體的數據類型

postForObject 案例

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

postForLocation 案例

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

這裏有點坑,咱們須要把這個uri添加到response的header中,否則後面拿到的是null。

exchange

exchange 方法和上述這些方法差異在於須要多一個請求類型的參數:

AsyncRestTemplate 異步客戶端

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

RestTemplate 攔截器

Spring提供了ClientHttpRequestInterceptor和AsyncClientHttpRequestInterceptor兩個接口,分別能夠對RestTemplate和AsyncRestTemplate發起的請求進行攔截,並在其被髮送至服務端以前修改請求或是加強相應的信息。

  • ClientHttpRequestInterceptor 攔截 RestTemplate

  • AsyncClientHttpRequestInterceptor 攔截AsyncRestTemplate

設置攔截器就是經過提供的 setInterceptors 設置便可:

自定義 ResponseErrorHandler

ResponseErrorHandler 接口定義了當response發生錯誤時須要進行的操做。這裏咱們自定義一個CustomResponseErrorHandler,當返回的code不是200時,就表示執行出錯了。

設置 ResponseErrorHandler:

執行結果:

處理流程

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

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

getForEntity 方法:

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

execute 方法:

  • 這個方法裏面是對url進行urlencode編碼處理的,統一轉爲URL。這裏咱們也能夠手動把參數進行網絡編碼。

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

  • createRequest
  • doWithRequest
  • execute
  • handleResponse

一、createRequest

這個方法的做用就是建立一個 ClientHttpRequest 對象。RestTemplate集成了 HttpAccessor這個抽象類,建立ClientHttpRequest的過程就是在其父類HttpAccessor中經過默認的 ClientHttpRequestFactory 實現類 SimpleClientHttpRequestFactory 完成具體的請求建立。

  • 一、建立 java.net.HttpURLConnection 對象

  • 二、設置 connection,包括 connectTimeout、setDoInput 等。

  • 三、bufferRequestBody 用於標誌是否使用緩存流的形式,默認是 true。缺點是當發送大量數據時,好比 put/post,存在內存消耗嚴重。該值能夠經過 SimpleClientHttpRequestFactory#setBufferRequestBody來修改。

不一樣版本的變動仍是比較大的,你們在閱讀源碼時,仍是從最新的代碼來看。

二、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,將請求消息寫入輸出流。

三、execute

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

這裏是使用實例 SimpleBufferingClientHttpRequest 封裝請求體和請求頭。從代碼中能夠看到:

  • delete 時經過前面設置的 DoOutput參數和是否能夠設置輸出流來判斷是否須要發送請求體若是是 delete 請求,那麼很明顯 DoOutput = false,不會有封裝請求體的過程,即不執行FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream())。

四、handleResponse

最後就是 response 的解析了,從代碼來看,主要仍是 Error 的解析。這裏的ErrorHandler咱們前面也提到,能夠經過實現 ResponseErrorHandler 來自定義 異常處理。

小結

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

相關文章
相關標籤/搜索