Spring RestTemplate 是 Spring 提供的用於訪問 Rest 服務的客戶端,RestTemplate 提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率,因此不少客戶端好比 Android或者第三方服務商都是使用 RestTemplate 請求 restful 服務。html
調用 RestTemplate 的默認構造函數,RestTemplate 對象在底層經過使用 java.net 包下的實現建立 HTTP 請求,能夠經過使用 ClientHttpRequestFactory 指定不一樣的HTTP請求方式。默認使用 SimpleClientHttpRequestFactory,是 ClientHttpRequestFactory 實現類。以下流程:java
1)使用默認構造方法new一個實例spring
RestTemplate template = new RestTemplate();數據庫
2)RestTemplate 內部經過調用 doExecute 方法,首先就是獲取 ClientHttpRequest緩存
3)RestTemplate 實現了抽象類 HttpAccessor ,因此能夠調用父類的 createRequestrestful
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();異步
public ClientHttpRequestFactory getRequestFactory() {函數
return this.requestFactory;post
}測試
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("Created " + method.name() + " request for \"" + url + "\"");
}
return request;
}
4)SimpleClientHttpRequestFactory 實現了 ClientHttpRequest,同時實現方法
注意 bufferRequestBody 是能夠在 RestTemplate 設置,是標誌是否使用緩存流的形式,默認是 true,缺點是當發送大量數據時,好比put/post的保存和修改,那麼可能內存消耗嚴重。因此這時候能夠設置 RestTemplate.setBufferRequestBody(false);
即便用 SimpleStreamingClientHttpRequest 來實現。
5)openConnection 沒什麼文章,而是 prepareConnection 則是大有文章,這裏咱們分兩個版原本說,由於咱們一開始使用 4.1.1 的時候不能使用帶請求體的delete,但是在 4.3.2 版本則可使用,因此特別區分了這兩個版本的代碼,以下:
SimpleClientHttpRequestFactory -- 4.1.1 版本的代碼默認
delete connection.setDoOutput = fase
若是設置false,而後後面又去獲取輸出流時,會發生以下錯誤 sun 包的 HttpURLConnection
if(!this.doOutput) {
throw new ProtocolException(
"cannot write to a URLConnection if doOutput=false - call setDoOutput(true)"
);
}
SimpleClientHttpRequestFactory -- 4.3.2 版本的代碼默認
delete connection.setDoOutput = fase
DoOutput 的屬性做用是可使用 conn.getOutputStream().write() ,這樣就能發送請求體了
6)接着執行 requestCallback.doWithRequest(request);
RequestCallback 封裝了請求體和請求頭對象,也就是說在該對象裏面能夠拿到咱們須要的請求參數,在執行 doWithRequest 時,有一個很是重要的步驟,他和前面Connection發送請求體有着密切關係,咱們知道請求頭就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那麼請求體 bufferedOutput 是如何賦值的呢?就是在 doWithRequest 裏面,以下 StringHttpMessageConverter (其餘 MessageConvert 也同樣,這裏也是常常亂碼的緣由)
其中 s 就是請求體,HttpOutputMessage 對象就是咱們準備的 ClientHttpRequest 對象,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest
這樣,先調用父類的流方法,把內容寫入流中,而後調用父類的 executeInternal方法在調用自身的該方法 executeInternal ,以下一步
7)接着執行 response = request.execute();
而後使用實例 SimpleBufferingClientHttpRequest 封裝請求體和請求頭
SimpleBufferingClientHttpRequest -- 4.1.1 版本的代碼默認
delete 時經過前面設置的 DoOutput 參數和是否能夠設置輸出流來判斷是否須要發送請求體
若是是 delete 請求,那麼很明顯 DoOutput = false,因此不會有封裝請求體的過程,即不執行
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
因此服務端沒法獲取到請求體,會出現 HttpMessageNotReadableException: Required request body is missing
SimpleBufferingClientHttpRequest -- 4.3.2 版本的代碼默認
delete 時經過請求方式和是否有請求體對象來判斷是否須要發送請求體
若是是delete請求,首先設置 DoOutput = true,而後根據是否有請求體數據,而後封裝請求體
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
8)最後解析response
接着就是 response 的解析了,主要仍是 Error 的解析。
handleResponseError(method, url, response);
1)setBufferRequestBody 是不是否緩衝流來存儲請求體,默認true
2)setProxy 設置代理對象
3)setChunkSize 設置每次傳輸字節長度,與 setBufferRequestBody(false) 結合使用
4)setConnectTimeout 設置鏈接超時時間,默認 -1
5)setReadTimeout 設置讀取內容超時時間,默認 -1
6)setOutputStreaming 設置Connection是否設置輸出流程
7)setTaskExecutor 設置異步回調執行器
其實任何有鏈接的地方都會有鏈接池的概念,好比數據庫鏈接等,這裏也不例外,確定也會有,RestTemplate 默認有兩種工廠對象實現方式,都是 ClientHttpRequestFactory 的子類。以下
1)SimpleClientHttpRequestFactory 底層使用 java.net.HttpUrlConnection,可配置證書
2)HttpComponentsClientHttpRequestFactory 底層使用Apache HttpClient訪問遠程的Http服務,使用HttpClient一樣能夠配置鏈接池和證書等信息,並且功能更強大,配置項更多。
1)使用XML配置,就是配置JavaBean
2)使用代碼配置,就是初始化這個對象
不管上面那種方式配置,都是配置外殼 RestTemplate,真正發送請求的 request 對象其實都是由工廠管理的,因此咱們不關心鏈接池的管理,只是配置鏈接池初始化的一些參數而已。
這個能夠參考:
http://www.open-open.com/lib/view/open1436018677419.html
HttpMessageNotReadableException: Required request body is missing
Spring MVC 的 @RequestBody 只支持RestTemplate 的 POST 和 PUT
可是 RestTemplate 的 delete 方法並不支持傳入請求體(Request Body)。經測試,經過調用 RestTemplate 類的exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<ResponseResult> responseType, Object... uriVariables) 方法,將 method 指定爲 org.springframework.http.HttpMethod.DELETE,並傳入 requestEntity(請求體) 對象時,在服務端獲得的 Request Body 仍然爲 null。可見 RestTemplate 默認並不支持對 DELETE 方法使用請求體。
經過查閱資料發現 RestTemplate 默認是使用 spring 自身的 SimpleClientHttpRequestFactory 建立請求對象和對其進行相關設置(如請求頭、請求體等),它只支持 PUT 和 POST 方法帶請求體,RestTemplate 的 DELETE 方法不支持傳入請求體是由於 JDK 中 HttpURLConnection 對象的 delete 方法不支持傳入請求體(若是對 HttpURLConnection 對象的 delete 方法傳入請求體,在運行時會拋出 IOException)。
從代碼中也看到了 Spring 對 delete 作了判斷,若是是 4.1.1 及之前的版本,確實是會出現上面的問題,可是當我使用 4.3.2 以後的版本,發現徹底能夠發送請求體,這裏面的變化就是前者在代碼中把請求體過濾掉了,後者把請求體加上了。至於更細的細節,但願有人可以繼續深究下去。