【Spring-web】RestTemplate源碼學習

 2016-12-22   by 安靜的下雪天  http://www.cnblogs.com/quiet-snowy-day/p/6210288.html

前言

在Web開發工做中,有一部分開發任務是不須要寫web頁面的。好比,本地服務在集成某些第三方的功能的時候(訪問其餘RESTful資源),經過轉發URL請求到第三方服務,獲取應答信息。這些應答信息不須要渲染到畫面上,而是返回給客戶端(APP或者其餘web應用)。本地服務對於第三方服務來講是客戶端;對於總體系統而言,就像是一箇中轉站。
這種開發內容除了業務邏輯,剩下的基本都是套路代碼,而Spring從3.0版本開始,爲咱們提供了封裝好的訪問HTTP的模板代碼RestTemplate。
 

RestTemplate 類說明

/** * <strong>Spring's central class for synchronous client-side HTTP access.</strong> * It simplifies communication with HTTP servers, and enforces RESTful principles. * It handles HTTP connections, leaving application code to provide URLs * (with possible template variables) and extract results. * * <p><strong>Note:</strong> by default the RestTemplate relies on standard JDK * facilities to establish HTTP connections. You can switch to use a different * HTTP library such as Apache HttpComponents, Netty, and OkHttp through the * {@link #setRequestFactory} property. * * <p>The main entry points of this template are the methods named after the six main HTTP methods: * <table> * <tr><th>HTTP method</th><th>RestTemplate methods</th></tr> * <tr><td>DELETE</td><td>{@link #delete}</td></tr> * <tr><td>GET</td><td>{@link #getForObject}</td></tr> * <tr><td></td><td>{@link #getForEntity}</td></tr> * <tr><td>HEAD</td><td>{@link #headForHeaders}</td></tr> * <tr><td>OPTIONS</td><td>{@link #optionsForAllow}</td></tr> * <tr><td>POST</td><td>{@link #postForLocation}</td></tr> * <tr><td></td><td>{@link #postForObject}</td></tr> * <tr><td>PUT</td><td>{@link #put}</td></tr> * <tr><td>any</td><td>{@link #exchange}</td></tr> * <tr><td></td><td>{@link #execute}</td></tr> </table> * * <p>In addition the {@code exchange} and {@code execute} methods are generalized versions of * the above methods and can be used to support additional, less frequent combinations (e.g. * HTTP PATCH, HTTP PUT with response body, etc.). Note however that the underlying HTTP * library used must also support the desired combination. * * <p>For each HTTP method there are three variants: two accept a URI template string * and URI variables (array or map) while a third accepts a {@link URI}. * Note that for URI templates it is assumed encoding is necessary, e.g. * {@code restTemplate.getForObject("http://example.com/hotel list")} becomes * {@code "http://example.com/hotel%20list"}. This also means if the URI template * or URI variables are already encoded, double encoding will occur, e.g. * {@code http://example.com/hotel%20list} becomes * {@code http://example.com/hotel%2520list}). To avoid that use a {@code URI} method * variant to provide (or re-use) a previously encoded URI. To prepare such an URI * with full control over encoding, consider using * {@link org.springframework.web.util.UriComponentsBuilder}. * * <p>Internally the template uses {@link HttpMessageConverter} instances to * convert HTTP messages to and from POJOs. Converters for the main mime types * are registered by default but you can also register additional converters * via {@link #setMessageConverters}. * * <p>This template uses a * {@link org.springframework.http.client.SimpleClientHttpRequestFactory} and a * {@link DefaultResponseErrorHandler} as default strategies for creating HTTP * connections or handling HTTP errors, respectively. These defaults can be overridden * through {@link #setRequestFactory} and {@link #setErrorHandler} respectively. * * @author Arjen Poutsma * @author Brian Clozel * @author Roy Clarkson * @author Juergen Hoeller * @since 3.0 * @see HttpMessageConverter * @see RequestCallback * @see ResponseExtractor * @see ResponseErrorHandler * @see AsyncRestTemplate */
Java Doc
嘗試翻譯以下:
RestTemplate是 Spring中客戶端同步訪問HTTP的核心類。它簡化了與HTTP服務器的通訊,執行RESTful原則。
它能處理HTTP連接,委託應用程序代碼(使用合適的模板變量)來裝配URL,並提取應答信息。
 
注意:默認狀況下,RestTemplate依賴標準JDK工具來建立HTTP連接。經過設置(HttpAccessor.setRequestFactory)屬性,你能夠轉而使用像Apache HttpComponents、Netty、OkHttp這樣的HTTP庫。
 
該模板類的主要切入點爲如下幾個方法(並對應着HTTP的六個主要方法):
另外,exchange和execute方法提供了以上方法的通用版本,用來支持額外的、不經常使用的組合(如:HTTP PATCH,帶有消息體的HTTP PUT,等等)。注意,不管怎樣使用底層HTTP庫,都必須支持必要的組合。
 
對於每一個HTTP方法都有3個變體:
其中兩個方法的接收參數是URI模式字符串和URI變量(array or map),第三個的接收參數是java.net.URI。
注意,須要爲URI模式串假定編碼格式,如:restTemplate.getForObject("http://example.com/hotel list") 變爲 "http://example.com/hotel%20list"
這同時也意味着,若是URI模式串或URI變量已經編碼,會產生重複編碼,如:"http://example.com/hotel%20list" 變成了 "http://example.com/hotel%2520list"
爲了不使用URI方法變體來提供(或重用)預編碼的URI,能夠考慮使用UriComponentsBuilder,來制定能夠徹底控制編碼的URI。
 
在模板內部使用HttpMessageConverter實例來實現HTTP消息與POJO類的相互轉換。
主要的MIME類型的轉換器已經默認註冊,你也能夠經過setMessageConverters(List<HttpMessageConverter<?>>)方法來註冊額外的轉換器。
 
本模板分別使用SimpleClientHttpRequestFactory 和 DefaultResponseErrorHandler做爲默認策略,來建立HTTP連接、處理HTTP錯誤。經過HttpAccessor.setRequestFactory(ClientHttpRequestFactory) 和 setErrorHandler(ResponseErrorHandler)方法能夠分別覆蓋以前的默認設置。
 

補充說明:

重載的3個方法怎麼選擇呢?建議選擇URL參數類型爲String的那個兩個方法。由於當這個參數是一個非URI格式的,須要進行轉換,而URI的構造函數會拋出一個檢查異常URISyntaxException,該異常必須捕獲。另外兩個重載方法則避免了捕獲異常,因此上面表格中推薦的方法的第一個參數都是String類型。html

 

簡單例子

寫了兩個測試方法,博主喜歡用Junit
package com.practice; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; import net.sf.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; public class RestTemplateTest extends TestCase { RestTemplate restTpl; @Before public void setUp() { restTpl = new RestTemplate(); } @Test public void testGet() { Map<String, Object> paramsMap = new HashMap<String, Object>(); paramsMap.put("cityCode", "xxxxxxxxxx"); paramsMap.put("key", "xxxxxxxxxxxxxxxxxxxxx"); String url = "http://xxx.xxx.xxx.xxx:8080/xxx/xxxx?xxxx&config=xxx&cityCode={cityCode}&key={key}"; String respStr = restTpl.getForObject(url, String.class, paramsMap); System.out.println(respStr); JSONObject respJson = restTpl.getForObject(url, JSONObject.class, paramsMap); System.out.println(respJson); } @Test public void testPost() throws Exception { String posturl = "http://xxx.xxx.xxx.xxx:8080/xxxx/xxxx/xxxxx"; JSONObject metadata = new JSONObject(); metadata.put("dddddd", "xxxxx"); metadata.put("ssssss", "xxxxxx"); metadata.put("flag", true); JSONObject paramsJson = new JSONObject(); paramsJson.put("mmmmm", "mmm"); paramsJson.put("nnnnn", "nnn"); paramsJson.put("password", "xxxxxxxxxxx"); paramsJson.put("metadata", metadata); String params = "RequestJson=" + URLEncoder.encode(paramsJson.toString(), "utf-8"); MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); HttpEntity<Object> hpEntity = new HttpEntity<Object>(params, headers); ResponseEntity<String> respEntity = restTpl.postForEntity(posturl, hpEntity, String.class); System.out.println(respEntity); } }
View Code

然而,就是這麼簡單的幾行代碼還不斷出錯~~~~~~~ java

以前postForEntity方法的第二個參數(請求消息體的類型)直接使用了JSONObject對象,發生瞭如下錯誤,
查看了spring-web源碼,發現源碼工程中引用的jackson-databind包的版本是2.8.1。改了jar包版本後,能夠繼續運行了。
 
然而,又錯了,見下圖。
此次是由於請求信息不完整,測試的服務端對POST消息體的格式有要求,而我沒有設置Content-Type,請求頭的信息只有AcceptContent-Length兩項。
建立Map對象並設定頭信息,改用HttpEntity做爲發送對象,終於順利執行了。

 

疑問解答

RestTemplate中是如何處理請求頭信息的呢?web

以GET方法爲例,getForObject()方法中有這麼一句【RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);】
其中 acceptHeaderRequestCallback 方法返回了 AcceptHeaderRequestCallback 類的實例。 
AcceptHeaderRequestCallback 是 RestTemplate 的內部類,實現了RequestCallback接口,該接口只有一個方法doWithRequest。
AcceptHeaderRequestCallback 實現了doWithRequest 方法:根據響應實體類型responseType,遍歷全部的消息轉換器,找到適合的,而後再從這些轉換器中找到全部支持的媒體類型,最後將全部支持的媒體類型設置到請求頭部Accept中。
再來看POST方法,postForEntity()方法中有【RequestCallback requestCallback = httpEntityCallback(request, responseType);】
其中 httpEntityCallback 方法返回了 HttpEntityRequestCallback 類的實例。
HttpEntityRequestCallback 也是RestTemplate 的內部類,它繼承了 AcceptHeaderRequestCallback 類,重寫了doWithRequest方法:首先調用父類doWithRequest方法,完成請求頭部設定;而後,根據響應實體類型,經過遍歷找到適合的消息轉換器;最後經過消息轉換器將POST消息體寫入請求實體中。

這裏爲何使用內部類呢?spring

複習下內部類的做用:json

1. 隱藏了實現細節。AcceptHeaderRequestCallback 和 HttpEntityRequestCallback 的訪問限定都是private;另外,在內部類中實現接口,而不是由 RestTemplate 類implement 多個接口,也就避免了對外部調用者暴露 doWithRequest 方法及其實現。
2. 內部類能夠訪問包含類的全部元素。在內部類的 doWithRequest 方法中訪問了 RestTemplate 類的成員變量messageConverters,該變量是全部消息轉換器的列表。
3. 多繼承,這裏是內部類的之間有繼承關係,也算間接多繼承?RestTemplate 類是繼承了 InterceptingHttpAccessor 類,實現了 RestOperations 接口。
4. 避免因父類和實現接口中有重名的方法,而進行沒必要要的修改。這點到是沒有明顯體現,RestTemplate的繼承類和兩個接口中沒有重名的方法。
 

請求和響應消息解析是怎麼解析的?
是否用到某種設計模式?
RestTemplate發送的是同步請求,那麼異步請求是如何處理呢?
感受這個坑越挖越深,須要花點時間好好看看……
不用發愁博客沒有內容可寫了:P
相關文章
相關標籤/搜索