- Spring Boot進階:原理、實戰與面試題分析
- 鄭天民
- 2562字
- 2022-07-05 09:41:52
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所示。

圖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所示。

圖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請求的完整流程就介紹完畢了。
- Oracle Exadata性能優化
- Dynamics 365 Application Development
- 架構不再難(全5冊)
- Java游戲服務器架構實戰
- The Data Visualization Workshop
- C語言程序設計學習指導與習題解答
- 青少年學Python(第1冊)
- Learning FuelPHP for Effective PHP Development
- Julia for Data Science
- Internet of Things with ESP8266
- Oracle實用教程
- NGUI for Unity
- Clojure for Finance
- SQL Server 2012數據庫管理與開發(慕課版)
- HTML5 Boilerplate Web Development