人圓月圓心圓,人和家和國和---中秋節快樂html
在閱讀本篇以前,建議先閱讀開山篇效果更佳。RestTemplate
是Spring提供的用於訪問Rest
服務的客戶端工具,它提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率。弱弱呼籲一句:對於那些在Spring
環境下還在使用HttpClient
(或其它Client)的同窗,今兒看完本文後,建議切換到RestTemplate
(有特殊需求的固然除外嘍~)。react
RestTemplate
簡化了與http服務的通訊,程序代碼能夠給它提供URL,並提取結果。它默認使用的JDK 的HttpURLConnection
進行通訊,然而咱們是能夠經過RestTemplate.setRequestFactory
切換到不一樣的HTTP源:如Apache HttpComponents
、Netty
、OkHttp
等等。程序員
指定一組基本restful操做的接口,定義了基本的Rest操做集合,它的惟一實現是RestTemplate
;不直接使用,但這是加強可測試性的一個有用選項,由於它很容易被模擬或存根(後面這句話請好好理解)。web
能夠對比參照
RedisOperations
,它的實現類也只有RedisTemplate
一個。他倆都採用了設計模式中的模板模式
ajax
因爲此接口裏的方法實在太多了(40+個),所以我按照Http標準進行分類以下表格:spring
// @since 3.0
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
...
}複製代碼
注意:使用字符串類型的url默認會對url進行轉義,如http://example.com/hotel list
在執行時會轉義爲http://example.com/hotel%20list
,隱式的轉義這樣是沒有問題的。但若是你本身已經轉義過了,那就不ok了。若不想要這種隱式的轉義,建議使用URI(URI uri = uriComponents.toUri()
)來構造。json
RestTemplate
中POST請求的三種方式==post
請求表明新建/建立一個資源,因此它是有返回值的。由於它的使用最爲複雜,所以本文以它爲例進行講解。設計模式
你若是熟練使用過瀏覽器的開發者工具
調試過,你確定知道POST
請求它傳參是有兩種方式的:數組
這兩種方式是經過Content-Type
來區別的:如果application/x-www-form-urlencoded
那就是formdata
方式;如果application/json
或者multipart/form-data
等方式那就是request payload
方式瀏覽器
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,手動邀請你入羣一塊兒飛 ==