人圓月圓心圓,人和家和國和---中秋節快樂
在閱讀本篇以前,建議先閱讀開山篇效果更佳。RestTemplate
是Spring提供的用於訪問Rest
服務的客戶端工具,它提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率。
弱弱呼籲一句:對於那些在Spring
環境下還在使用HttpClient
(或其它Client)的同窗,今兒看完本文後,建議切換到RestTemplate
(有特殊需求的固然除外嘍~)。html
RestTemplate
簡化了與http服務的通訊,程序代碼能夠給它提供URL,並提取結果。它默認使用的JDK 的HttpURLConnection
進行通訊,然而咱們是能夠經過RestTemplate.setRequestFactory
切換到不一樣的HTTP源:如Apache HttpComponents
、Netty
、OkHttp
等等。java
指定一組基本restful操做的接口,定義了基本的Rest操做集合,它的惟一實現是RestTemplate
;不直接使用,但這是加強可測試性的一個有用選項,由於它很容易被模擬或存根(後面這句話請好好理解)。react
能夠對比參照RedisOperations
,它的實現類也只有RedisTemplate
一個。他倆都採用了設計模式中的模板模式
因爲此接口裏的方法實在太多了(40+個),所以我按照Http標準進行分類以下表格:程序員
// @since 3.0 public enum HttpMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; ... }
HttpMethod | 方法 |
---|---|
GET | |
HEAD | |
POST | |
PUT | |
PATCH | |
DELETE | |
OPTIONS | |
TRACE | 無 |
any(執行任何Http方法) |
觀察發現,雖然方法衆多但有很強的規律可循。每一個方法都有三種重載實現:2種的url參數爲字符串,一種URI參數,因此掌握規律後再使用,就不用懼怕它的多而不知咋使用了。web
xxxForObject:返回響應體(也就直接是body體力的內容) (T)
xxxForEntity:返回的相應行、響應頭、響應碼、響應體等等 (ResponseEntity<T>)
xxxForLocation:提交成功以後,返回新資源的URI。這個只須要服務提供者返回一個 URI 便可,該 URI 表示新資源的位置,可謂很是輕量。 (URI)
注意:使用字符串類型的url默認會對url進行轉義,如http://example.com/hotel list
在執行時會轉義爲http://example.com/hotel%20list
,隱式的轉義這樣是沒有問題的。但若是你本身已經轉義過了,那就不ok了。
若不想要這種隱式的轉義,建議使用URI(URI uri = uriComponents.toUri()
)來構造。ajax
RestTemplate
中POST請求的三種方式==post
請求表明新建/建立一個資源,因此它是有返回值的。由於它的使用最爲複雜,所以本文以它爲例進行講解。spring
你若是熟練使用過瀏覽器的開發者工具
調試過,你確定知道POST
請求它傳參是有兩種方式的:json
這兩種方式是經過Content-Type
來區別的:如果application/x-www-form-urlencoded
那就是formdata
方式;如果application/json
或者multipart/form-data
等方式那就是request payload
方式segmentfault
jQuery
在執行post請求時,默認會給你設置Content-Type
爲application/x-www-form-urlencoded
,因此服務器可以正確解析。
若使用js原生的ajax,若是不顯示的
設置Content-Type,那麼默認是text/plain,這時服務器就不知道怎麼解析數據了,因此才只能經過獲取原始數據流的方式來進行解析請求數據。(相信沒人這麼幹吧~)
exchange方法:更通用的請求方法。它入參必須接受一個RequestEntity
,從而能夠設置請求的路徑、頭等等信息,最終全都是返回一個ResponseEntity
(能夠發送Get、Post、Put等全部請求)。
execute方法:最最最底層、通用的請求方法。設計模式
RequestCallback:用於操做請求頭和body,在請求發出前
執行;ResponseExtractor:解析/提取HTTP響應的數據,並且不須要擔憂異常和資源的關閉
RequestCallback.doWithRequest(ClientHttpRequest)
說白了就是拿到ClientHttpRequest
後對他進行繼續處理~
RestTemplate
的acceptHeaderRequestCallback、httpEntityCallback
這些方法能夠設置它~
這兩個抽象類不容忽視,HystrixCommand和Ribbon
的邏輯都和它有關係(攔截器)。HttpAccessor
是個抽象基類,它定義要操做ClientHttpRequestFactory
的公共屬性,它通常不直接使用。
// @since 3.0 public abstract class HttpAccessor { // RestTemplate默認使用的客戶端工廠:基於源生JDK private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 若要切換成三方庫的底層組件,設置此方法即可 public void setRequestFactory(ClientHttpRequestFactory requestFactory) { this.requestFactory = requestFactory; } ... // get方法 // 供給子類很是方便的拿到一個ClientHttpRequest protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { ClientHttpRequest request = getRequestFactory().createRequest(url, method); return request; } }
它的子類是:InterceptingHttpAccessor
,也仍是個抽象實現,主要是管理起了請求的攔截器們:ClientHttpRequestInterceptor
。
// @since 3.0 // @see InterceptingClientHttpRequestFactory public abstract class InterceptingHttpAccessor extends HttpAccessor { // 裝載須要做用在RestTemplate上的攔截器們~~~ private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(); @Nullable private volatile ClientHttpRequestFactory interceptingRequestFactory; // 這裏語意是set,因此是徹底的替換掉(支持ordered排序哦~~~) public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { if (this.interceptors != interceptors) { this.interceptors.clear(); this.interceptors.addAll(interceptors); AnnotationAwareOrderComparator.sort(this.interceptors); } } // 複寫了父類的這個方法頗有意思 // 意思爲:若你調用者手動set進來了,那就以調用者設置的工廠爲準 不然使用的是InterceptingClientHttpRequestFactory @Override public void setRequestFactory(ClientHttpRequestFactory requestFactory) { super.setRequestFactory(requestFactory); this.interceptingRequestFactory = null; } // 若配置了攔截器,那麼默認就使用InterceptingClientHttpRequestFactory,而再也不是SimpleClientHttpRequestFactory了~~~ @Override public ClientHttpRequestFactory getRequestFactory() { List<ClientHttpRequestInterceptor> interceptors = getInterceptors(); if (!CollectionUtils.isEmpty(interceptors)) { ClientHttpRequestFactory factory = this.interceptingRequestFactory; if (factory == null) { factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors); this.interceptingRequestFactory = factory; } return factory; } else { return super.getRequestFactory(); } } }
InterceptingHttpAccessor
最主要的處理邏輯爲:若發現調用者設置了請求攔截器,那麼它建立的工廠是具備攔截功能的InterceptingClientHttpRequestFactory
,不然就是默認的SimpleClientHttpRequestFactory
。
InterceptingClientHttpRequestFactory
工廠它產生的ClientHttpRequest
是InterceptingClientHttpRequest
,然而它就會執行攔截器的攔截方法嘍:nextInterceptor.intercept(request, body, this)
提問:若有配置有多個請求攔截器,都會執行嗎?
解答:這個千萬不要犯迷糊和輕易下結論:覺得沒有迭代它(for循環)而只是iterator.next()
就覺得如有多個就只會執行一個,那就大錯特錯了。這裏實際是造成了一個執行鏈條,只要攔截器的intercept
方法內最終還調用執行器的intercept()
方法,那麼攔截器鏈就會一直執行下去。其根本原因是第三個參數傳入的是this
,至始至終都是同一個執行器(this=InterceptingRequestExecution
)
RestTemplate
採用同步方式執行 HTTP 請求的類,底層默認使用JDK
原生 HttpURLConnection API
。它實現了接口RestOperations
,提供了很是多的模版方法(重載方法)讓開發者能更簡單地發送 HTTP 請求。
須要注意的是,RestTemplate
是Spring 3.0
就有了,但在Spring5.0後,Spring官方是推薦使用org.springframework.web.reactive.function.client.WebClient
替代它,特別是對於異步的場景。
RestTemplate
由於使用極其普遍,so即便到了Spring 5.0,官方只是建議替代,但並無標註@Deprecated
,所以至少目前你還能夠想咋用就咋用吧。
可是AsyncRestTemplate
是明確標註了@Deprecated
,強烈建議使用org.springframework.web.reactive.function.client.WebClient
去代替,因此在5.0後不建議再使用它了~。
固然還須要說明一點:若你的項目中沒有使用到WebFlux
的技術棧來處理請求,那麼也不必說爲了使用而使用,因此不必專門爲了它而導包(我的建議)~
// @since 3.0 public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { // 去classpath探測 是否有這些消息轉換器相關的jar~ // 通常狀況下咱們都會導jackson2Present~~~ private static boolean romePresent; private static final boolean jaxb2Present; private static final boolean jackson2Present; private static final boolean jackson2XmlPresent; private static final boolean jackson2SmilePresent; private static final boolean jackson2CborPresent; private static final boolean gsonPresent; private static final boolean jsonbPresent; ... // 下面四個變量很重要: // 消息轉換器們(顯然對JSON格式默認是支持得最好的) private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); // 默認的請求異常處理器,Spring5.0後其實可使用它ExtractingResponseErrorHandler // 它可以利用消息換換氣提取你的錯誤內容。而且還支持自定義錯誤碼、錯誤序列等等~ private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler(); // 用於URL的構建 private UriTemplateHandler uriTemplateHandler; // 默認的返回值提取器~~~~ private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor(); // 空構造,應該是平時使用得最多的了:一切都使用默認的組件配置Resource等等 public RestTemplate() { // 這個幾個消息轉換器是支持的。字節數組、字符串、 this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter(false)); this.messageConverters.add(new SourceHttpMessageConverter<>()); // 對form表單提交方式的支持 this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); // 接下里即是一些列的判斷,若類路徑上有才會加進來 if (jackson2Present) { this.messageConverters.add(new MappingJackson2HttpMessageConverter()); } ... // new DefaultUriBuilderFactory() this.uriTemplateHandler = initUriTemplateHandler(); } // 你懂的,若想用OkHttp,也能夠在構造時就指定 public RestTemplate(ClientHttpRequestFactory requestFactory) { this(); setRequestFactory(requestFactory); } // 若不想用默認的消息轉換器,也能夠本身指定(其實通常都不這麼去幹,而是後面本身再add進來) public RestTemplate(List<HttpMessageConverter<?>> messageConverters) { Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required"); this.messageConverters.addAll(messageConverters); this.uriTemplateHandler = initUriTemplateHandler(); } ... // 省略上面屬性的get/set犯法們 }
這部分源碼我列出來,都是在對構建一個RestTemplate
實例的準備工做相關方法,包括對各個相關組件的設置。
接下來更重要的即是它實現的接口方法了,我抽出一些關鍵點進行描述說明:
RestTemplate: @Override @Nullable public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { //一、new AcceptHeaderRequestCallback(responseType) 它能在發送請求的以前這樣一件事: // request.getHeaders().setAccept(allSupportedMediaTypes) RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); // 最終調用的是execute方法,此時URL是個字符串 // responseExtractor返回值提取器使用的是消息轉換器去讀取body噠~ // 返回值就是返回的body自己(不含有返回的響應頭等等信息~) return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } // 它返回的是ResponseEntity,不會返回null的 最終調用的依舊是execute方法 // 此時候用的就不是消息轉換器的提取器了,而是內部類`ResponseEntityResponseExtractor`(底層仍是依賴消息轉換器) // 可是這個提取器,提取出來的可都是ResponseEntity<T>實例~ @Override public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); } // HEAD請求:很簡單,使用的提取器就是headersExtractor,從返回值裏把響應header拿出來便可 @Override public HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException { return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables)); } // POST請求 @Override @Nullable public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException { // 一、HttpEntityRequestCallback 適配:把request適配成一個HttpEntity // 而後執行前,經過消息轉換器把頭信息、body信息等等都write進去 RequestCallback requestCallback = httpEntityCallback(request); // 由於須要拿到URI,因此此處使用headersExtractor提取器先拿到響應的header便可~~~ HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables); return (headers != null ? headers.getLocation() : null); } // 除了httpEntityCallback()不同,其他和get請求同樣 @Override @Nullable public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } // PUT請求:由於沒有返回值,因此不須要返回值提取器。因此,很是的簡單~~~ @Override public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); } // DELETE請求:也是木有返回值的。 // 而且請注意:DELETE請求這裏可都是不能接收body的,不能給請求設置請求體的 // (雖然可能底層httpCLient支持,但這裏不支持,請遵照規範) @Override public void delete(String url, Object... uriVariables) throws RestClientException { execute(url, HttpMethod.DELETE, null, null, uriVariables); } // OPTIONS請求:和HEAD請求的處理邏輯幾乎同樣 @Override public Set<HttpMethod> optionsForAllow(String url, Object... uriVariables) throws RestClientException { ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor(); HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables); return (headers != null ? headers.getAllow() : Collections.emptySet()); }
全部方法大致執行邏輯一致,都是和RequestCallback
、responseExtractor
等有關,且最終都是委託給了最爲底層的execute()
方法去執行。
你是否疑問:它提供的put方法返回值都是void,若我put請求就有返回值腫麼辦呢?那麼接下來就介紹更爲通用的一個方法:exchange()
RestTemplate: @Override public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { // 把請求體適配爲HttpEntity RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); // 消息提取器使用ResponseEntityResponseExtractor ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); // 從上兩個部分就能看到:exchange方法的入參、出參都是很是通用的~~~ return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } // ParameterizedTypeReference參數化類型,用於處理泛型 // 上面的responseType就是個Class。這裏是個參數化類型~~~~~ @Override public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException { Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(requestEntity, type); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } // 這個方法就很是精簡了,讓調用者本身去構造RequestEntity,裏面是包含了請求的URL和方法等信息的 @Override public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor)); }
exchange
全部方法使用的都是HttpEntity
和ResponseEntity
表明請求實體和響應實體,足以見到它設計的通用性。
在Spring3.2後提供了
ParameterizedTypeReference
來處理參數化類型---> 主要是爲了處理List等的泛型
能夠發現即便是exchange()
方法,最終仍是委託給execute/doExecute
去執行的:
RestTemplate: // 3個execute方法。最終調用的都是doExecute方法 // 它作的一件事:使用UriTemplateHandler把URL的參數填進去~~~ // 底層使用的是我上文介紹的`UriComponentsBuilder`,仍是比較簡單的 @Override @Nullable public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); } doExecute方法: @Nullable protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { ClientHttpResponse response = null; ClientHttpRequest request = createRequest(url, method); // 若是有回調,那就先回調處理一會兒請求 if (requestCallback != null) { requestCallback.doWithRequest(request); } // 真正意義上的發送請求。 // 請注意:若是這裏的request是`InterceptingClientHttpRequest`,那就回執行攔截器的intercept方法哦~~~ // 至於何時是InterceptingClientHttpRequest呢?這個上面有講的 response = request.execute(); // 處理結果(如有錯誤,那就拋出異常~~~) handleResponse(url, method, response); // 請求正常。那就使用返回值提取器responseExtractor提取出內容便可了~~~ return (responseExtractor != null ? responseExtractor.extractData(response) : null); ... // 關閉響應(ClientHttpResponse繼承了Closeable接口) finally { if (response != null) { response.close(); } } }
看完doExecute()
的模板式的實現步驟,就清楚了RestTemplate
從發出一個請求到收到一個響應的完整過程。Spring
設計了多個相關組件,提供鉤子程序讓咱們能夠干預到流程裏面去,最多見的固然就是請求攔截器了,它在Ribbon負載均衡和Hystrix熔斷器裏面有很好的應用~
它是@since 4.0
新增的用於解決一些異步Http請求的場景,但它壽命比較短,在Spring5.0
就標記爲@Deprecated
,而被推薦使用WebClient
去代替它。
它的實現基礎原理是:RestTemplate
+ SimpleAsyncTaskExecutor
任務池的方式去實現的異步請求,返回值均爲ListenableFuture
。掌握了RestTemplate
後,它使用起來是沒有什麼障礙的
看過了原理的描述,我有理由相信你已經爛熟於胸並對RestTemplate
可以運用自如了。所以關於使用方面,本文只給以下很是簡單的一個Demo Show我認爲是夠了的:
public static void main(String[] args) throws IOException { RestTemplate restTemplate = new RestTemplate(); String pageHtml = restTemplate.getForObject("http://www.baidu.com", String.class); System.out.println(pageHtml); // 百度首頁的html... }
解釋一點:這裏請求獲得的是一個html網頁,因此HttpMessageConverterExtractor
去提取響應時,使用的是StringHttpMessageConverter
去處理的,提取代碼以下:
StringHttpMessageConverter: @Override protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException { // 從響應頭的contentType裏提取(如果application/json,那默認也是urf-8) // 若沒有指定編碼,就取值getDefaultCharset。好比本處訪問百度,就取值默認值`ISO-8859-1`對body體進行編碼的~ Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); return StreamUtils.copyToString(inputMessage.getBody(), charset); }
小夥伴把此請求案例能夠和上面我使用ClientHttpRequestFactory
發送請求的案例對比(或者和你本身使用HttpClient
步驟對比),感覺感覺使用RestTemplate
是多麼的優雅~
RestTemplate組件:ClientHttpRequestFactory、ClientHttpRequestInterceptor、ResponseExtractor【享學Spring MVC】
爲什麼一個@LoadBalanced註解就能讓RestTemplate擁有負載均衡的能力?【享學Spring Cloud】
微服務做爲主流的今天,RestTemplate
可謂是一把利器,每一個程序員都應該掌握它。深刻理解它對實際應用、調優都有很現實的意義,因此我相信本文可以幫助到你,作到爛熟於胸。
預告一下:下篇文章會原理分析告訴你們爲什麼一個簡單的@LoadBalanced
註解就能讓RestTemplate
擁有負載均衡的能力?
== 若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入羣一塊兒飛 ==
== 若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入羣一塊兒飛 ==