RestTemplate的使用和原理你都爛熟於胸了嗎?【享學Spring MVC】

每篇一句

人圓月圓心圓,人和家和國和---中秋節快樂

前言

在閱讀本篇以前,建議先閱讀開山篇效果更佳。RestTemplate是Spring提供的用於訪問Rest服務的客戶端工具,它提供了多種便捷訪問遠程Http服務的方法,可以大大提升客戶端的編寫效率
弱弱呼籲一句:對於那些在Spring環境下還在使用HttpClient(或其它Client)的同窗,今兒看完本文後,建議切換到RestTemplate (有特殊需求的固然除外嘍~)。html

RestTemplate簡化了與http服務的通訊,程序代碼能夠給它提供URL,並提取結果。它默認使用的JDK 的HttpURLConnection進行通訊,然而咱們是能夠經過RestTemplate.setRequestFactory切換到不一樣的HTTP源:如Apache HttpComponentsNettyOkHttp等等。java

RestOperations

指定一組基本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

  1. Form Data方式:咱們用from表單提交的方式就是它;使用ajax(注意:這裏指的是jQuery的ajax,而不是源生js的)默認的提交方式也是它~

在這裏插入圖片描述

  1. request payload方式:多部分方式/json方式

在這裏插入圖片描述
在這裏插入圖片描述

這兩種方式是經過Content-Type來區別的:如果application/x-www-form-urlencoded那就是formdata方式;如果application/json或者multipart/form-data等方式那就是request payload方式segmentfault

jQuery在執行post請求時,默認會給你設置 Content-Typeapplication/x-www-form-urlencoded,因此服務器可以正確解析。
若使用js原生的ajax,若是不 顯示的設置Content-Type,那麼默認是text/plain,這時服務器就不知道怎麼解析數據了,因此才只能經過獲取原始數據流的方式來進行解析請求數據。(相信沒人這麼幹吧~)

exchange和execute方法:

exchange方法:更通用的請求方法。它入參必須接受一個RequestEntity,從而能夠設置請求的路徑、頭等等信息,最終全都是返回一個ResponseEntity(能夠發送Get、Post、Put等全部請求)。
execute方法:最最最底層、通用的請求方法。設計模式

RequestCallback:用於操做請求頭和body,在請求發出 執行;ResponseExtractor:解析/提取HTTP響應的數據,並且不須要擔憂異常和資源的關閉
RequestCallback.doWithRequest(ClientHttpRequest)說白了就是拿到 ClientHttpRequest後對他進行繼續處理~
RestTemplateacceptHeaderRequestCallback、httpEntityCallback這些方法能夠設置它~

HttpAccessor、InterceptingHttpAccessor

這兩個抽象類不容忽視,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

InterceptingHttpAccessor

// @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工廠它產生的ClientHttpRequestInterceptingClientHttpRequest,然而它就會執行攔截器的攔截方法嘍:nextInterceptor.intercept(request, body, this)

提問:若有配置有多個請求攔截器,都會執行嗎?
解答:這個千萬不要犯迷糊和輕易下結論:覺得沒有迭代它(for循環)而只是iterator.next()就覺得如有多個就只會執行一個,那就大錯特錯了。這裏實際是造成了一個執行鏈條,只要攔截器的intercept方法內最終還調用執行器的intercept()方法,那麼攔截器鏈就會一直執行下去。其根本原因是第三個參數傳入的是this,至始至終都是同一個執行器(this=InterceptingRequestExecution




==RestTemplate==

RestTemplate採用同步方式執行 HTTP 請求的類,底層默認使用JDK原生 HttpURLConnection API 。它實現了接口RestOperations,提供了很是多的模版方法(重載方法)讓開發者能更簡單地發送 HTTP 請求。

須要注意的是,RestTemplateSpring 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全部方法使用的都是HttpEntityResponseEntity表明請求實體和響應實體,足以見到它設計的通用性。

在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熔斷器裏面有很好的應用~

AsyncRestTemplate

它是@since 4.0新增的用於解決一些異步Http請求的場景,但它壽命比較短,在Spring5.0就標記爲@Deprecated,而被推薦使用WebClient去代替它。

它的實現基礎原理是:RestTemplate + SimpleAsyncTaskExecutor任務池的方式去實現的異步請求,返回值均爲ListenableFuture。掌握了RestTemplate後,它使用起來是沒有什麼障礙的

極簡使用Demo Show

看過了原理的描述,我有理由相信你已經爛熟於胸並對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,手動邀請你入羣一塊兒飛 ==

相關文章
相關標籤/搜索