官术网_书友最值得收藏!

4.1.3 RestTemplate遠程調用原理分析

在4.1.2節中,我們詳細描述了如何使用RestTemplate訪問HTTP端點,涉及RestTemplate初始化、發起請求以及獲取響應結果等核心環節。在本節中,我們將基于這些步驟,從源碼出發深入理解RestTemplate實現遠程調用的底層原理。

1. 遠程調用主流程

我們先來看一下RestTemplate類的定義,如代碼清單4-15所示。

代碼清單4-15 RestTemplate類的定義代碼

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations

可以看到,RestTemplate擴展了InterceptingHttpAccessor抽象類,并實現了RestOperations接口。我們圍繞RestTemplate的定義來梳理它在設計上的思想。

首先,我們來看RestOperations接口的定義,這里截取了部分核心方法,如代碼清單4-16所示。

代碼清單4-16 RestOperations接口定義代碼

public interface RestOperations {
    <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
    <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
    <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
    void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException;
    void delete(String url, Object... uriVariables) throws RestClientException;
    <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
    Class<T> responseType, Object... uriVariables) throws RestClientException;
    ...
}

顯然,正是這個RestOperations接口定義了所有get/post/put/delete/exchange等遠程調用方法組,而這些方法都是遵循RESTful架構風格而設計的。RestTemplate對這些接口都提供了實現,這是它的一條代碼支線。

然后,我們再來看InterceptingHttpAccessor,它是一個抽象類,包含的核心變量如代碼清單4-17所示。

代碼清單4-17 InterceptingHttpAccessor中的核心變量定義代碼

public abstract class InterceptingHttpAccessor extends HttpAccessor {
    private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    private volatile ClientHttpRequestFactory interceptingRequestFactory;
    ...
}

通過變量定義,我們明確了InterceptingHttpAccessor應該包含兩部分處理功能,一部分是設置和管理請求攔截器ClientHttpRequestInterceptor,另一部分則是獲取用于創建客戶端HTTP請求的工廠類ClientHttpRequestFactory。這是RestTemplate的另一條代碼支線。

同時,我們注意到InterceptingHttpAccessor同樣存在一個父類HttpAccessor,這個父類值得展開討論一下,因為它真正完成了ClientHttpRequestFactory的創建以及通過ClientHttpRequestFactory獲取了代表客戶端請求的ClientHttpRequest對象。HttpAccessor的核心變量如代碼清單4-18所示。

代碼清單4-18 HttpAccessor中的核心變量定義代碼

public abstract class HttpAccessor {
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpReques-tFactory();
    ...
}

可以看到,HttpAccessor創建了SimpleClientHttpRequestFactory作為系統默認的Client-HttpRequestFactory。關于ClientHttpRequestFactory,本節還會進行詳細的討論。

作為總結,我們來梳理一下RestTemplate的基本類層結構,如圖4-1所示。

085-1

圖4-1 RestTemplate的類層結構

通過RestTemplate的類層結構,我們可以理解它的設計思想。整個類層結構可以清晰地分成兩條線,左邊部分用于完成與HTTP請求相關的實現機制,而右邊部分則提供了RESTful風格的操作入口,并使用了面向對象的接口和抽象類完成了對這兩部分功能的聚合。

介紹完RestTemplate的實例化過程,接下來我們來分析它的核心執行流程。對于遠程調用的模板工具類,我們可以從具備多種請求方式的exchange()方法入手,該方法如代碼清單4-19所示。

代碼清單4-19 exchange()方法實現代碼

@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
    //構建請求回調
    RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
    //構建響應體提取器
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtra-ctor(responseType);
    //執行遠程調用
    return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}

顯然,我們應該進一步關注這里的execute()方法。事實上,無論我們采用get/put/post/delete方法組中的哪個方法來發起請求,RestTemplate負責執行遠程調用的都是這個execute()方法,該方法定義如代碼清單4-20所示。

代碼清單4-20 execute()方法定義代碼

@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);
}

execute()方法首先通過UriTemplateHandler構建了一個URI,然后將請求過程委托給了doExecute()方法進行處理,該方法定義如代碼清單4-21所示。

代碼清單4-21 doExecute()方法定義代碼

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    Assert.notNull(url, "URI is required");
    Assert.notNull(method, "HttpMethod is required");
    ClientHttpResponse response = null;
    try {
        //創建請求對象
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
            //執行對請求的回調
            requestCallback.doWithRequest(request);
        }
        //獲取調用結果
        response = request.execute();
        //處理調用結果
        handleResponse(url, method, response);
        //從結果中提取數據
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }
    catch (IOException ex) {
        ...
    }
    finally {
        if (response != null) {
            response.close();
        }
    }
}

從上述方法中,我們可以清晰地看到使用RestTemplate進行遠程調用所涉及的三大步驟,即創建請求對象、執行遠程調用以及處理響應結果。讓我們一起來分別看一下。

2. 創建請求對象

創建請求對象的入口方法如代碼清單4-22所示。

代碼清單4-22 創建請求對象的入口方法代碼

ClientHttpRequest request = createRequest(url, method);

分析這里的createRequest()方法,我們發現流程執行到了前面介紹的HttpAccessor類,如代碼清單4-23所示。

代碼清單4-23 HttpAccessor類實現代碼

public abstract class HttpAccessor {
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpReques-tFactory();
    ...
    protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = getRequestFactory().createRequest(url, method);
        ...
        return request;
    }
}

創建ClientHttpRequest的過程是一種典型的工廠模式應用場景,這里直接創建了一個實現ClientHttpRequestFactory接口的SimpleClientHttpRequestFactory對象,然后再通過這個對象的createRequest()方法創建了客戶端請求對象ClientHttpRequest,并返回給上層組件進行使用。ClientHttpRequestFactory接口的定義如代碼清單4-24所示。

代碼清單4-24 ClientHttpRequestFactory接口定義代碼

public interface ClientHttpRequestFactory {
    //創建客戶端請求對象
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

在Spring Boot中,存在一批ClientHttpRequestFactory接口的實現類,SimpleClient-HttpRequestFactory是它的默認實現,開發人員也可以根據需要實現自定義的ClientHttp-RequestFactory。簡單起見,我們直接跟蹤SimpleClientHttpRequestFactory的代碼,來到它的createRequest()方法,如代碼清單4-25所示。

代碼清單4-25 SimpleClientHttpRequestFactory的createRequest()方法代碼

@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
    prepareConnection(connection, httpMethod.name());

    if (this.bufferRequestBody) {
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
    }
    else {
        return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
    }
}

上述createRequest()方法中,首先通過傳入的URI對象構建了一個HttpURLConnection對象,然后對該對象進行一些預處理,最后構造并返回一個ClientHttpRequest的實例。

通過翻閱代碼,我們發現在上述代碼中只是簡單地通過URL對象的openConnection()方法返回了一個UrlConnection對象。而在prepareConnection()方法中,也只是完成了對HttpUrlConnection中超時時間、請求方法等常見屬性的設置。

3. 執行遠程調用

一旦獲取了請求對象,就可以發起遠程調用并獲取響應結果了,RestTemplate中的入口方法如代碼清單4-26所示。

代碼清單4-26 通過RestTemplate獲取響應結果代碼

response = request.execute();

這里的request就是前面創建的SimpleBufferingClientHttpRequest類,我們可以先來看一下該類的類層結構,如圖4-2所示。

088-1

圖4-2 SimpleBufferingClientHttpRequest類層結構

在圖4-2的AbstractClientHttpRequest抽象類中,定義了如代碼清單4-27所示的execute()方法。

代碼清單4-27 AbstractClientHttpRequest的execute()方法代碼

@Override
public final ClientHttpResponse execute() throws IOException {
    assertNotExecuted();
    ClientHttpResponse result = executeInternal(this.headers);
    this.executed = true;
    return result;
}

protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;

AbstractClientHttpRequest類的作用就是防止HTTP請求的Header和Body被多次寫入,所以在這個execute()方法返回之前設置了executed標志位。同時,在execute()方法中,最終調用了一個抽象方法executeInternal(),而這個方法的實現是在AbstractClientHttpRequest的子類AbstractBufferingClientHttpRequest中,如代碼清單4-28所示。

代碼清單4-28 AbstractBufferingClientHttpRequest的executeInternal ()方法代碼

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
    byte[] bytes = this.bufferedOutput.toByteArray();
    if (headers.getContentLength() < 0) {
        headers.setContentLength(bytes.length);
    }
    ClientHttpResponse result = executeInternal(headers, bytes);
    this.bufferedOutput = new ByteArrayOutputStream(0);
    return result;
}

protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException;

和AbstractClientHttpRequest類一樣,這里進一步梳理了一個抽象方法executeInternal(),而這個抽象方法則由最底層的SimpleBufferingClientHttpRequest類來實現,如代碼清單4-29所示。

代碼清單4-29 SimpleBufferingClientHttpRequest的executeInternal ()方法代碼

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    addHeaders(this.connection, headers);
    if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
        this.connection.setDoOutput(false);
    }
    if (this.connection.getDoOutput() && this.outputStreaming) {
    this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
    }
    this.connection.connect();
    if (this.connection.getDoOutput()) {
        FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
    }
    else {
        this.connection.getResponseCode();
    }
    return new SimpleClientHttpResponse(this.connection);
}

這里通過FileCopyUtils.copy()工具方法將響應結果寫入到輸出流上。而executeInternal()方法最終返回的是一個包裝了Connection對象的SimpleClientHttpResponse。

4. 處理響應結果

一個HTTP請求處理的最后一步就是從ClientHttpResponse中讀取輸入流,格式化成一個響應體并將其轉化為業務對象,如代碼清單4-30所示。

代碼清單4-30 從ClientHttpResponse中提取結果數據代碼

//處理調用結果
handleResponse(url, method, response);
//從結果中提取數據
return (responseExtractor != null ? responseExtractor.extractData(response) : null);

我們先來看這里的handleResponse()方法,如代碼清單4-31所示。

代碼清單4-31 handleResponse()方法代碼

protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
    ResponseErrorHandler errorHandler = getErrorHandler();
    boolean hasError = errorHandler.hasError(response);
    if (logger.isDebugEnabled()) {
        ...
    }
    if (hasError) {
        errorHandler.handleError(url, method, response);
    }
}

這段代碼實際上并沒有真正處理返回的數據,而只是執行了對錯誤的處理。通過getErrorHandler()方法獲取了一個ResponseErrorHandler,如果響應的狀態碼是錯誤的,那么就調用handleError處理錯誤并拋出異常。

那么,獲取響應數據并完成轉化的工作就應該是在ResponseExtractor中,該接口定義如代碼清單4-32所示。

代碼清單4-32 ResponseExtractor接口定義代碼

public interface ResponseExtractor<T> {
    @Nullable
    T extractData(ClientHttpResponse response) throws IOException;
}

在RestTemplate類中,定義了一個ResponseEntityResponseExtractor內部類來實現Response-Extractor接口,如代碼清單4-33所示。

代碼清單4-33 ResponseEntityResponseExtractor類代碼

private class ResponseEntityResponseExtractor <T> implements ResponseExtractor<ResponseEntity<T>> {
    @Nullable
    private final HttpMessageConverterExtractor<T> delegate;
    public ResponseEntityResponseExtractor(@Nullable Type responseType) {
        if (responseType != null && Void.class != responseType) {
            this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
        }
        else {
            this.delegate = null;
        }
    }

    @Override
    public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
        if (this.delegate != null) {
            T body = this.delegate.extractData(response);
            return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
        }
        else {
            return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
        }
    }
}

可以看到,ResponseEntityResponseExtractor中的extractData()方法本質上是將數據提取部分的工作委托給了一個代理對象delegate,而delegate的類型就是HttpMessageConverter-Extractor。從命名上看,我們不難聯想,在HttpMessageConverterExtractor類的內部,勢必使用了HttpMessageConverter來完成消息的轉換。其核心邏輯就是遍歷HttpMessageConveter列表,然后判斷其是否能夠讀取數據,如果能就調用read()方法讀取數據。

最后,我們來討論一下HttpMessageConveter中的read()方法是如何實現的。讓我們來看HttpMessageConveter接口的抽象實現類AbstractHttpMessageConverter,在它的read()方法中同樣定義了一個抽象方法readInternal(),如代碼清單4-34所示。

代碼清單4-34 AbstractHttpMessageConverter的read()方法代碼

@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    return readInternal(clazz, inputMessage);
}

protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

Spring Boot內置了一系列的HttpMessageConveter來完成消息的轉換,這里面最簡單的就是StringHttpMessageConverter,該類的read()方法如代碼清單4-35所示。

代碼清單4-35 StringHttpMessageConverter的read()方法代碼

@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
    Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
    return StreamUtils.copyToString(inputMessage.getBody(), charset);
}

StringHttpMessageConverter的實現過程就是從輸入消息HttpInputMessage中通過getBody()方法獲取消息體,也就是獲取一個ClientHttpResponse對象;然后通過copy-ToString()方法從該對象中讀取數據,并返回字符串結果。

至此,通過RestTemplate發起、執行以及響應整個HTTP請求的完整流程就介紹完畢了。

主站蜘蛛池模板: 三原县| 兴安县| 岳池县| 加查县| 涟源市| 华亭县| 光山县| 樟树市| 子洲县| 枣阳市| 兴城市| 湘阴县| 民乐县| 繁昌县| 久治县| 赞皇县| 龙口市| 壤塘县| 图木舒克市| 汕头市| 永善县| 阿坝县| 武定县| 三原县| 湛江市| 滦南县| 清徐县| 馆陶县| 九龙城区| 怀柔区| 闸北区| 泗洪县| 会东县| 延川县| 乡宁县| 麻阳| 巍山| 涪陵区| 万源市| 瑞昌市| 洪洞县|