文章目錄
- 前言
- 正文
- 一、書接上回,從代理對象入手
- 二、ReflectiveFeign.FeignInvocationHandler#invoke()
- 三、SynchronousMethodHandler#invoke(...) 的實現原理
- 3.1 invoke(...)源碼
- 3.2 executeAndDecode(...) 執行請求并解碼
- 四、如何更換client 的實現
- 附錄
- 附1:本系列文章鏈接
- 附2:比較HttpURLConnection、Apache HttpClient、OkHttp
前言
本篇是SpringCloud原理系列的 OpenFeign 模塊的第四篇。
在我們啟動完應用后,Spring容器也初始化好了很多我們用到的類。(什么,你不知道,煩請先看看第三篇)
那么我們下一步要做的就是,發出rest請求,然后調用FeignClient標注的接口方法。這篇文章,我們就來看看它的原理。
本文關鍵詞:RequestTemplate
、SynchronousMethodHandler
使用java 17,spring cloud 4.0.4,springboot 3.1.4
使用項目是本系列第一篇中的項目
正文
一、書接上回,從代理對象入手
第三篇文章時,我們看到了SpringCloud將 OpenFeign的接口,映射為一個代理對象。
打個比方,使用如下接口:
@FeignClient(name = "helloFeignClient", url = "http://localhost:10080")
public interface HelloFeignClient {@PostMapping("/hello/post")HelloResponse postHello(@RequestBody HelloRequest helloRequest);
}
最終生成的代理對象是對 HelloFeignClient
接口的代理,并且綁定了handler。handler的類型是ReflectiveFeign.FeignInvocationHandler
換句話說,就是當我們調用接口HelloFeignClient
中的方法時,會觸發調用ReflectiveFeign.FeignInvocationHandler
的invoke(...)
方法。
二、ReflectiveFeign.FeignInvocationHandler#invoke()
查看源碼可以知道,這里invoke
方法,實際是先從 dispatch
中找到對應方法的真正的處理器,然后進行調用。
從第三篇文章,我們能知道 dispatch 是對 method 的映射。
比如接口HelloFeignClient
會被映射為dispatch
,一個方法對應為一對key、value值。dispatch
的類型是:
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
也就是說Method 只是作為一個橋梁,連接起了HelloFeignClient
內的方法和真正執行的handler實例。這里的實例真正的實現是SynchronousMethodHandler
。也就是說,當我們調用接口方法時,會執行SynchronousMethodHandler#invoke(...)
。
三、SynchronousMethodHandler#invoke(…) 的實現原理
3.1 invoke(…)源碼
public Object invoke(Object[] argv) throws Throwable {// 創建請求模板,包裝請求頭、請求體,url等字段參數RequestTemplate template = this.buildTemplateFromArgs.create(argv);// 獲取連接超時等參數Request.Options options = this.findOptions(argv);// 重試Retryer retryer = this.retryer.clone();while(true) {try {// 執行請求并解碼return this.executeAndDecode(template, options);} catch (RetryableException var9) {RetryableException e = var9;try {retryer.continueOrPropagate(e);} catch (RetryableException var8) {Throwable cause = var8.getCause();if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {throw cause;}throw var8;}if (this.logLevel != Level.NONE) {this.logger.logRetry(this.metadata.configKey(), this.logLevel);}}}}
3.2 executeAndDecode(…) 執行請求并解碼
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {// 通過模版獲取請求體,執行所有請求攔截器Request request = this.targetRequest(template);if (this.logLevel != Level.NONE) {this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);}long start = System.nanoTime();Response response;try {// 使用客戶端執行請求response = this.client.execute(request, options);// 使用響應建造器構造一個響應體,包含請求和請求模板response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException var9) {if (this.logLevel != Level.NONE) {this.logger.logIOException(this.metadata.configKey(), this.logLevel, var9, this.elapsedTime(start));}throw FeignException.errorExecuting(request, var9);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);// 處理響應結果&記錄日志&響應解碼return this.responseHandler.handleResponse(this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);}
通過分析,發現是先創建了RequestTemplate 實例,然后調用了client實例進行遠程調用。而client的實現有多個,我這邊看到內部實現了一個默認的:
public static class Default implements Client {public Response execute(Request request, Request.Options options) throws IOException {HttpURLConnection connection = this.convertAndSend(request, options);return this.convertResponse(connection, request);}
}
也就是說,到了這一步,就涉及到遠程連接了。
這里用的是比較原始的HttpURLConnection
。每次都創建新的連接,去請求,然后斷開連接。這樣很多時間也就浪費在建立連接等操作上了。而且調用量一旦變大,很容易出錯。
問題來了,有沒有什么辦法能優化下呢?
四、如何更換client 的實現
上文提到 HttpURLConnection
是默認的連接方式。那麼我們有什么優化方案嗎?
可替代方案一般有兩種,一種是帶有連接池的Apache HttpClient
,另一種是協議上占有優勢的 OkHttp
。
至于它們的更詳細的優缺點,以及不同之處,請查看本文的附2。
另外,我的下一篇文打算單獨將這塊寫一下 ===> SpringCloud實用-OpenFeign整合okHttp
戳附錄中的【本系列文章鏈接】查看文章。
附錄
附1:本系列文章鏈接
SpringCloud系列文章目錄(總綱篇)
附2:比較HttpURLConnection、Apache HttpClient、OkHttp
參考:七大主流的HttpClient程序比較
Client | 優點 | 缺點 |
---|---|---|
HttpURLConnection | jdk自帶、原始、簡單 | 缺乏連接池管理、域名機械控制等特性支持,性能&效率較低,一般不建議使用 |
Apache HttpClient (已經停止開發) Apache HttpComponents HttpClient | 1. 支持連接池、多線程 2. 易用,靈活 | 安卓社區不再使用它,替換為了okHttp 需要自己做一層封裝 |
java.net.http.HttpClient | java11正式啟用,替代原先的HttpURLConnection | 如果使用的版本是java11以下的,用不了它 |
okHttp | 性能方面與HttpClient基本一樣 鏈接復用 Response 緩存和 Cookie 默認 GZIP 請求失敗自動重連 DNS 擴展 Http2/SPDY/WebSocket 協議支持 默認情況下,OKHttp會自動處理常見的網絡問題:像二次連接、SSL的握手問題。 從Android4.4開始HttpURLConnection的底層實現采用的是okHttp. | |
一般情況下,如果使用了SpringCloud,基本都會選擇 OpenFeign+okHttp的組合。