RestTemplate組件:ClientHttpRequestFactory、ClientHttpRequestInterceptor、ResponseExtractor【享學Spring MVC】
- 2019 年 10 月 5 日
- 筆記
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/f641385712/article/details/100713622
每篇一句
做事的人和做夢的人最大的區別就是行動力
前言
本文為深入了解Spring
提供的Rest調用客戶端RestTemplate
開山,對它相關的一些組件做講解。
Tips:請注意區分
RestTemplate
和RedisTemplate
哦~
ClientHttpRequestFactory
它是個函數式介面,用於根據URI
和HttpMethod
創建出一個ClientHttpRequest
來發送請求~
ClientHttpRequest
它代表請求的客戶端,該介面繼承自HttpRequest
、HttpOutputMessage
,只有一個ClientHttpResponse execute() throws IOException
方法。其中Netty、HttpComponents、OkHttp3,HttpUrlConnection
對它都有實現~
// @since 3.0 RestTemplate這個體系都是3.0後才有的 @FunctionalInterface public interface ClientHttpRequestFactory { // 返回一個ClientHttpRequest,這樣調用其execute()方法就可以發送rest請求了~ ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException; }
它的繼承樹如下:

可以直觀的看到,我們可以使用Apache
的HttpClient
、OkHttp3
、Netty4
都可,但這些都需要額外導包,默認情況下Spring
使用的是java.net.HttpURLConnection
。
HttpClient最新版本:4.5.10 OkHttp最新版本:4.1.1(雖然版本號是4,但是GAV還是3哦:com.squareup.okhttp3) Netty最新版本:4.1.39.Final(它的5版本可以宣告已死)
Spring4.0
是新增了一個對非同步支援的AsyncClientHttpRequestFactory
(Spring5.0後標記為已廢棄):
// 在Spring5.0後被標記為過時了,被org.springframework.http.client.reactive.ClientHttpConnector所取代(但還是可用的嘛) @Deprecated public interface AsyncClientHttpRequestFactory { // AsyncClientHttpRequest#executeAsync()返回的是ListenableFuture<ClientHttpResponse> // 可見它的非同步是通過ListenableFuture實現的 AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException; }
使用工廠創建ClientHttpRequest
,然後我們發請求就不用關心具體httpClient內部的細節了(可插拔使用二方庫、三方庫)
SimpleClientHttpRequestFactory
它是Spring
內置默認的實現,使用的是JDK內置的java.net.URLConnection
作為client客戶端。
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { private static final int DEFAULT_CHUNK_SIZE = 4096; @Nullable private Proxy proxy; //java.net.Proxy private boolean bufferRequestBody = true; // 默認會緩衝body // URLConnection's connect timeout (in milliseconds). // 若值設置為0,表示永不超時 @see URLConnection#setConnectTimeout(int) private int connectTimeout = -1; // URLConnection#setReadTimeout(int) // 超時規則同上 private int readTimeout = -1; //Set if the underlying URLConnection can be set to 'output streaming' mode. private boolean outputStreaming = true; // 非同步的時候需要 @Nullable private AsyncListenableTaskExecutor taskExecutor; ... // 省略所有的set方法 @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { // 打開一個HttpURLConnection HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); // 設置超時時間、請求方法等一些參數到connection prepareConnection(connection, httpMethod.name()); //SimpleBufferingClientHttpRequest的excute方法最終使用的是connection.connect(); // 然後從connection中得到響應碼、響應體~~~ if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); } } // createAsyncRequest()方法略,無非就是在執行緒池裡非同步完成請求 ... }
需要注意的是:JDK <1.8 doesn't support getOutputStream with HTTP DELETE
,也就是說如果JDK
的版本低於1.8的話,那麼Delete請求是不支援body體的。
Demo Show:
public static void main(String[] args) throws IOException { SimpleClientHttpRequestFactory clientFactory = new SimpleClientHttpRequestFactory(); // ConnectTimeout只有在網路正常的情況下才有效,因此兩個一般都設置 clientFactory.setConnectTimeout(5000); //建立連接的超時時間 5秒 clientFactory.setReadTimeout(5000); // 傳遞數據的超時時間(在網路抖動的情況下,這個參數很有用) ClientHttpRequest client = clientFactory.createRequest(URI.create("https://www.baidu.com"), HttpMethod.GET); // 發送請求 ClientHttpResponse response = client.execute(); System.out.println(response.getStatusCode()); //200 OK System.out.println(response.getStatusText()); // OK System.out.println(response.getHeaders()); // // 返回內容 是個InputStream byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody()); System.out.println(new String(bytes, StandardCharsets.UTF_8)); // 百度首頁內容的html }
關於HttpURLConnection
的API使用,需注意如下幾點:
HttpURLConnection
對象不能直接構造,需要通過URL類中的openConnection()
方法來獲得HttpURLConnection
的connect()函數,實際上只是建立了一個與伺服器的TCP連接,並沒有實際發送HTTP請求。HTTP請求實際上直到我們獲取伺服器響應數據(如調用getInputStream()、getResponseCode()等方法)時才正式發送出去 1. 配置資訊都需要在connect()方法執行之前完成HttpURLConnection
是基於HTTP協議的,其底層通過socket通訊實現。如果不設置超時(timeout),在網路異常的情況下,可能會導致程式僵死而不繼續往下執行。請務必100%設置- HTTP正文的內容是通過OutputStream流寫入的, 向流中寫入的數據不會立即發送到網路,而是存在於記憶體緩衝區中,待流關閉時,根據寫入的內容生成HTTP正文
- 調用getInputStream()方法時,返回一個輸入流,用於從中讀取伺服器對於HTTP請求的返回資訊。
HttpURLConnection.connect()
不是必須的。當我們需要返回值時,比如我們使用HttpURLConnection.getInputStream()方法的時候它就會自動發送請求了,所以完全沒有必要調用connect()方法了(沒必要先建立Tcp嘛~)。
使用哪一個底層http庫?
我們知道HttpURLConnection
它在功能上是有些不足的(簡單的提交參數可以滿足)。絕大部分情況下Web站點的網頁可能沒這麼簡單,這些頁面並不是通過一個簡單的URL就可訪問的,可能需要用戶登錄而且具有相應的許可權才可訪問該頁面。在這種情況下,就需要涉及Session、Cookie的處理了,如果打算使用HttpURLConnection來處理這些細節,當然也是可能實現的,只是處理起來難度就大了。
這個時候,Apache開源組織提供了一個HttpClient
項目,可以用於發送HTTP請求,接收HTTP響應(包含HttpGet、HttpPost…等各種發送請求的對象)。
它不會快取伺服器的響應,不能執行HTML頁面中嵌入的Javascript程式碼;也不會對頁面內容進行任何解析、處理
因此,下面我就讓Spring使用HttpClient為示例演示使用三方庫: 1、導包
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.10</version> </dependency>
Tips:Requires Apache HttpComponents
4.3
or higher, as of Spring 4.0.
2、案例使用 案例內容僅僅只需
把上例第一句話換成使用HttpComponentsClientHttpRequestFactory
它的實例,其餘都不用變化即可成功看到效果。可以看看這個類它具體做了什麼
// @since 3.1 3.1後出現的。 public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { private HttpClient httpClient; @Nullable private RequestConfig requestConfig; // 這個配置就是可以配置超時等等亂七八糟client屬性的類 private boolean bufferRequestBody = true; //=========下面是構造函數們========= public HttpComponentsClientHttpRequestFactory() { // HttpClientBuilder.create().useSystemProperties().build(); // 所有若是這裡,配置超時時間可以這麼來設置也可: // System.setProperty(」sun.net.client.defaultConnectTimeout」, 「5000″); this.httpClient = HttpClients.createSystem(); } // 當然可以把你配置好了的Client扔進來 public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) { this.httpClient = httpClient; } ... // 省略設置超時時間。。。等等屬性的一些get/set // 超時資訊啥的都是保存在`RequestConfig`里的 @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpClient client = getHttpClient(); // 拿到你指定的client)=(或者系統預設的) // switch語句邏輯:HttpMethod == GET --> HttpGet HEAD --> HttpHead ... HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); postProcessHttpRequest(httpRequest); ... } }
實際使用的是HttpClient
完成的請求。另外OkHttp3ClientHttpRequestFactory
使用的是okhttp3.OkHttpClient
發送請求;Netty4ClientHttpRequestFactory
使用的是io.netty.channel.EventLoopGroup
。此處就不一一例舉了
Spring5.0以後,
Netty4ClientHttpRequestFactory
過期了,建議使用org.springframework.http.client.reactive.ReactorClientHttpConnector
代替~
關於HttpURLConnection
、HttpClient
、OkHttpClient
的簡單比較:
HttpURLConnection
: – 優點:JDK內置支援,java的標準類 – 缺點:API不夠友好,什麼都沒封裝,用起來太原始,不方便(這其實有時候也算優點,原始就證明好控~)HttpClient
: – 優點:功能強大,API友好,使用率夠高,幾乎成為了實際意義上的標準(相當於對HttpURLConnection
的封裝) – 缺點:性能稍低(比HttpURLConnection
低,但4.3後使用連接池進行了改善),API較臃腫,其實Android已經棄用了它~OkHttpClient
:新一代的Http訪問客戶端 – 優點:一個專註於性能和易用性的HTTP客戶端(節約寬頻,Android推薦使用),它設計的首要目標就是高效。提供了最新的 HTTP 協議版本 HTTP/2 和 SPDY 的支援。如果 HTTP/2 和 SPDY 不可用,OkHttp 會使用連接池來複用連接以提高效率 – 暫無。

關於Apache HttpClient
,Android
5.0之後已經廢棄使用它了(API太多,太重),推薦使用更輕量的HttpUrlConnection
。(Java開發還是推薦用HttpClient
)
OkHttp
優點較多:支援SPDY,可以合併多個到同一個主機的請求;OkHttp實現的諸多技術如:連接池,gziping,快取等;OkHttp 處理了很多網路疑難雜症:會從很多常用的連接問題中自動恢復。如果您的伺服器配置了多個IP地址,當第一個IP連接失敗的時候,OkHttp會自動嘗試下一個IP;OkHttp是一個Java的HTTP+SPDY客戶端開發包,同時也支援Android。默認情況下,OKHttp會自動處理常見的網路問題,像二次連接、SSL的握手問題。支援文件上傳、下載、cookie、session、https證書等幾乎所有功能。支援取消某個請求
綜上所述,不管是Java還是Android,我推薦的自然都是OkHttp
(OkHttp使用Okio進行數據傳輸。都是Square公司自家的,Square公司還出了一個Retrofit庫配合OkHttp戰鬥力翻倍)~~~
池化技術一般用於長連接,那麼像Http這種適合連接池嗎? HttpClient 4.3以後中使用了
PoolingHttpClientConnectionManager
連接池來管理持有連接,同一條TCP鏈路上,連接是可以復用的。HttpClient通過連接池的方式進行連接持久化(所以它這個連接池其實是tcp的連接池。它裡面有一個很重要的概念:Route
的概念,代表一條線路。比如baidu.com是一個route,163.com是一個route…)。連接池:可能是http請求,也可能是https請求 加入池話技術,就不用每次發起請求都新建一個連接(每次連接握手三次,效率太低)
AbstractClientHttpRequestFactoryWrapper
對其它ClientHttpRequestFactory
的一個包裝抽象類,它有如下兩個子類實現
InterceptingClientHttpRequestFactory
(重要)
Interceptor
攔截的概念,還是蠻重要的。它持有的ClientHttpRequestInterceptor
對於我們若想要攔截髮出去的請求非常之重要(比如全鏈路壓測中,可以使用它設置token之類的~)
// @since 3.1 public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { // 持有所有的請求攔截器 private final List<ClientHttpRequestInterceptor> interceptors; public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, @Nullable List<ClientHttpRequestInterceptor> interceptors) { super(requestFactory); // 攔截器只允許通過構造函數設置進來,並且並沒有提供get方法方法~ this.interceptors = (interceptors != null ? interceptors : Collections.emptyList()); } // 此處返回的是一個InterceptingClientHttpRequest,顯然它肯定是個ClientHttpRequest嘛~ @Override protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) { return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod); } }
InterceptingClientHttpRequest
的execute()
方法的特點是:若存在攔截器,交給給攔截器去執行發送請求return nextInterceptor.intercept(request, body, this)
,否則就自己上。
ClientHttpRequestInterceptor
關於請求攔截器,Spring MVC內置了兩個最基礎的實現

BasicAuthorizationInterceptor:
// @since 4.3.1 但在Spring5.1.1後推薦使用BasicAuthenticationInterceptor @Deprecated public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor { private final String username; private final String password; // 注意:username不允許包含:這個字元,但是密碼是允許的 public BasicAuthorizationInterceptor(@Nullable String username, @Nullable String password) { Assert.doesNotContain(username, ":", "Username must not contain a colon"); this.username = (username != null ? username : ""); this.password = (password != null ? password : ""); } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 用戶名密碼連接起來後,用Base64對位元組碼進行編碼~ String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8)); // 放進請求頭:key為`Authorization` 然後執行請求的發送 request.getHeaders().add("Authorization", "Basic " + token); return execution.execute(request, body); } }
這個攔截器木有對body有任何改動,只是把用戶名、密碼幫你放進了請求頭上。
需要注意的是:若你的header里已經存在了
Authorization
這個key,這裡也不會覆蓋的,這會添加哦。但並不建議你有覆蓋現象~
BasicAuthenticationInterceptor: 它是用來代替上類的。它使用標準的授權頭來處理,參考HttpHeaders#setBasicAuth、HttpHeaders#AUTHORIZATION
public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor { private final String username; private final String password; // 編碼,一般不用指定 @Nullable private final Charset charset; ... // 構造函數略 @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); // 只有當請求里不包含`Authorization`這個key的時候,此處才會設置授權頭哦 if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { // 這個方法是@since 5.1之後才提供的~~~~~ // 若不包含此key,就設置標準的授權頭(根據用戶名、密碼) 它內部也有這如下三步: // String credentialsString = username + ":" + password; // byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset)); // String encodedCredentials = new String(encodedBytes, charset); // 注意:它內部最終還是調用set(AUTHORIZATION, "Basic " + encodedCredentials);這個方法的 headers.setBasicAuth(this.username, this.password, this.charset); } return execution.execute(request, body); } }
說明:這兩個請求攔截器雖是Spring提供,但默認都是沒有被"裝配"的,所親需要,請手動裝配~
BufferingClientHttpRequestFactory
包裝其它ClientHttpRequestFactory
,使得具有快取的能力。若開啟快取功能(有開關可控),會使用BufferingClientHttpRequestWrapper
包裝原來的ClientHttpRequest
。這樣發送請求後得到的是BufferingClientHttpResponseWrapper
響應。
ResponseErrorHandler
用於確定特定響應是否有錯誤的策略介面。
// @since 3.0 public interface ResponseErrorHandler { // response里是否有錯 boolean hasError(ClientHttpResponse response) throws IOException; // 只有hasError = true時才會調用此方法 void handleError(ClientHttpResponse response) throws IOException; // @since 5.0 default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { handleError(response); } }
繼承樹如下:

DefaultResponseErrorHandler
Spring
對此策略介面的默認實現,RestTemplate
默認使用的錯誤處理器就是它。
// @since 3.0 public class DefaultResponseErrorHandler implements ResponseErrorHandler { // 是否有錯誤是根據響應碼來的,所以請嚴格遵守響應碼的規範啊 // 簡單的說4xx和5xx都會被認為有錯,否則是無錯的 參考:HttpStatus.Series @Override public boolean hasError(ClientHttpResponse response) throws IOException { int rawStatusCode = response.getRawStatusCode(); HttpStatus statusCode = HttpStatus.resolve(rawStatusCode); return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode)); } ... // 處理錯誤 @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); if (statusCode == null) { throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); } handleError(response, statusCode); } // protected方法,子類對它有複寫 protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { String statusText = response.getStatusText(); HttpHeaders headers = response.getHeaders(); byte[] body = getResponseBody(response); // 拿到body,把InputStream轉換為位元組數組 Charset charset = getCharset(response); // 注意這裡的編碼,是從返回的contentType里拿的~~~ // 分別針對於客戶端錯誤、服務端錯誤 包裝為HttpClientErrorException和HttpServerErrorException進行拋出 // 異常內包含有狀態碼、狀態text、頭、body、編碼等等資訊~~~~ switch (statusCode.series()) { case CLIENT_ERROR: throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset); case SERVER_ERROR: throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset); default: throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset); } } ... }
到這裡就可以給大家解釋一下,為何經常能看到客戶端錯誤,然後還有狀態碼+一串資訊了,就是因為這兩個異常。
HttpClientErrorException:
public class HttpClientErrorException extends HttpStatusCodeException { ... public static HttpClientErrorException create( HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { switch (statusCode) { case BAD_REQUEST: return new HttpClientErrorException.BadRequest(statusText, headers, body, charset); case UNAUTHORIZED: return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset); case FORBIDDEN: return new HttpClientErrorException.Forbidden(statusText, headers, body, charset); case NOT_FOUND: return new HttpClientErrorException.NotFound(statusText, headers, body, charset); case METHOD_NOT_ALLOWED: return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset); case NOT_ACCEPTABLE: return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset); case CONFLICT: return new HttpClientErrorException.Conflict(statusText, headers, body, charset); case GONE: return new HttpClientErrorException.Gone(statusText, headers, body, charset); case UNSUPPORTED_MEDIA_TYPE: return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset); case TOO_MANY_REQUESTS: return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset); case UNPROCESSABLE_ENTITY: return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset); default: return new HttpClientErrorException(statusCode, statusText, headers, body, charset); } } ... }
它針對不同的狀態碼HttpStatus
,創建了不同的類型進行返回,方便使用者控制,這在監控上還是蠻有意義的
BadRequest、Unauthorized、Forbidden…等等都是
HttpClientErrorException
的子類
HttpServerErrorException
程式碼類似,略~
ExtractingResponseErrorHandler
繼承自DefaultResponseErrorHandler
。在RESTful
大行其道的今天,Spring5.0
開始提供了此類。它將http錯誤響應利用HttpMessageConverter
轉換為對應的RestClientException
// @since 5.0 它出現得還是很晚的。繼承自DefaultResponseErrorHandler // 若你的RestTemplate想使用它,請調用RestTemplate#setErrorHandler(ResponseErrorHandler)設置即可 public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler { private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList(); // 對響應碼做快取 private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap<>(); private final Map<HttpStatus.Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap<>(); // 構造函數、set方法給上面兩個Map賦值。因為我們可以自己控制哪些狀態碼應該報錯,哪些不應該了~ // 以及可以自定義:那個狀態碼拋我們自定義的異常,哪一系列狀態碼拋我們自定義的異常,這個十分的便於我們做監控 ... // 省略構造函數和set方法。。。 // 增加快取功能~~~ 否則在交給父類 @Override protected boolean hasError(HttpStatus statusCode) { if (this.statusMapping.containsKey(statusCode)) { return this.statusMapping.get(statusCode) != null; } else if (this.seriesMapping.containsKey(statusCode.series())) { return this.seriesMapping.get(statusCode.series()) != null; } else { return super.hasError(statusCode); } } // 這個它做的事:extract:提取 @Override public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { if (this.statusMapping.containsKey(statusCode)) { extract(this.statusMapping.get(statusCode), response); } else if (this.seriesMapping.containsKey(statusCode.series())) { extract(this.seriesMapping.get(statusCode.series()), response); } else { super.handleError(response, statusCode); } } private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException { if (exceptionClass == null) { return; } // 這裡使用到了ResponseExtractor返回值提取器,從返回值里提取內容(本文是提取異常) HttpMessageConverterExtractor<? extends RestClientException> extractor = new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters); RestClientException exception = extractor.extractData(response); if (exception != null) { // 若提取到了異常資訊,拋出即可 throw exception; } } }
若你想訂製請求異常的處理邏輯,你也是可以自定義這個介面的實現的,當然還是建議你通過繼承DefaultResponseErrorHandler
來擴展~
ResponseExtractor
響應提取器:從Response
中提取數據。RestTemplate
請求完成後,都是通過它來從ClientHttpResponse
提取出指定內容(比如請求頭、請求Body體等)~

它的直接實現似乎只有HttpMessageConverterExtractor
,當然它也是最為重要的一個實現,和HttpMessageConverter
相關。 在解釋它之前,先看看這個:MessageBodyClientHttpResponseWrapper
,它的特點:它不僅可以通過實際讀取輸入流來檢查響應是否有消息體,還可以檢查其長度是否為0(即空)
// @since 4.1.5 它是一個訪問許可權是default的類,是對其它ClientHttpResponse的一個包裝 class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse { private final ClientHttpResponse response; // java.io.PushbackInputStream @Nullable private PushbackInputStream pushbackInputStream; // 判斷相應里是否有body體 // 若響應碼是1xx 或者是204;或者getHeaders().getContentLength() == 0 那就返回false 否則返回true public boolean hasMessageBody() throws IOException { HttpStatus status = HttpStatus.resolve(getRawStatusCode()); if (status != null && (status.is1xxInformational() || status == HttpStatus.NO_CONTENT || status == HttpStatus.NOT_MODIFIED)) { return false; } if (getHeaders().getContentLength() == 0) { return false; } return true; } // 上面是完全格局狀態碼(ContentLength)來判斷是否有body體的~~~這裡會根據流來判斷 // 如果response.getBody() == null,返回true // 若流里有內容,最終就用new PushbackInputStream(body)包裝起來~~~ public boolean hasEmptyMessageBody() throws IOException { ... } ... // 其餘介面方法都委託~ @Override public InputStream getBody() throws IOException { return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody()); } }
它的作用就是包裝後,提供兩個方法hasMessageBody、hasEmptyMessageBody
方便了對body體內容進行判斷
// @since 3.0 泛型T:the data type public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> { // java.lang.reflect.Type private final Type responseType; // 這個泛型也是T,表示數據的Class嘛~ // 該calss有可能就是上面的responseType @Nullable private final Class<T> responseClass; // 重要:用於消息解析的轉換器 private final List<HttpMessageConverter<?>> messageConverters; ... // 省略構造函數 // 從ClientHttpResponse 里提取值 @Override @SuppressWarnings({"unchecked", "rawtypes", "resource"}) public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); // 若沒有消息體(狀態碼不對 或者 消息體為空都被認為是木有) if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } // content-type若響應頭header里沒有指定,那默認是它MediaType.APPLICATION_OCTET_STREAM MediaType contentType = getContentType(responseWrapper); // 遍歷所有的messageConverters,根據contentType 來選則一個消息轉換器 // 最終return messageConverter.read((Class) this.responseClass, responseWrapper) ... } }
它的處理邏輯理解起來非常簡單:利用contentType
找到一個消息轉換器,最終HttpMessageConverter.read()
把消息讀出來轉換成Java對象。
它還有兩個內部類的實現如下(都是RestTemplate
的私有內部類):
RestTemplate: // 提取為`ResponseEntity` 最終委託給HttpMessageConverterExtractor完成的 private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> { @Nullable private final HttpMessageConverterExtractor<T> delegate; public ResponseEntityResponseExtractor(@Nullable Type responseType) { // 顯然:只有請求的返回值不為null 才有意義~ if (responseType != null && Void.class != responseType) { this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); } else { this.delegate = null; } } // 數據提取。都是交給`delegate.extractData(response)`做了,然後new一個ResponseEntity出來包裝進去 // 若木有返回值(delegate=null),那就是一個`ResponseEntity`實例,body為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(); } } } // 提取請求頭 private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> { @Override public HttpHeaders extractData(ClientHttpResponse response) { return response.getHeaders(); } }
UriTemplateHandler
這個組件它用於定義用變數擴展uri模板的方法。
// @since 4.2 出現較晚 // @see RestTemplate#setUriTemplateHandler(UriTemplateHandler) public interface UriTemplateHandler { URI expand(String uriTemplate, Map<String, ?> uriVariables); URI expand(String uriTemplate, Object... uriVariables); }
關於URI的處理,最終都是委託給UriComponentsBuilder
來完成。若對這塊還存在一定疑問的,強烈強烈強烈 參考這裡
推薦閱讀
RestTemplate的使用和原理你都爛熟於胸了嗎?【享學Spring MVC】
總結
本文介紹的組件是去理解RestTemplate
必備的組件們,屬於開山篇。因為RestTemplate
使用頻繁,並且經常需要調優,因此我寄希望大家也能對它做較為深入的了解,這也是我寫本系列的目的,共勉。