Feign源码解析4:调用过程
背景
前面幾篇分析了Feign的初始化過程,歷經艱難,可算是把@FeignClient注解的接口對應的代理對象給創建出來了。今天看下在實際Feign調用過程中的一些源碼細節。
我們這里Feign接口如下:
@FeignClient(value = "echo-service-provider") // 指向服務提供者應用
public interface EchoService {
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);
}
調用代碼:
http://localhost:8080/feign/echo/ddd
@GetMapping("/feign/echo/{message}")
public String feignEcho(@PathVariable String message) {
return echoService.echo(message);
}
完整解析
動態代理
這里的echoService此時的類型其實是jdk動態代理,內部有一個字段,就是實現了jdk的InvocationHandler接口:
實際邏輯如下,會根據被調用的method找到一個合適的handler:
import feign.InvocationHandlerFactory.MethodHandler
static class FeignInvocationHandler implements InvocationHandler{
private final Map<Method, MethodHandler> dispatch;
}
一般獲取到的都是如下類型:
這個類有如下核心屬性:
都是和http請求息息相關的,如重試、請求攔截器、響應攔截器、logger、logger級別、options(包含了超時時間等參數)。
重試
接下來看實際請求的大體框架:
上面主要是一個while循環,內部會執行請求,如果請求報錯,拋出了RetryableException類型的異常,此時就會由重試組件(Retryer retryer),判斷是否要重試,如果要的話,就會continue,就會再次執行請求。
但如果重試組件認為不需要重試或重試次數已經超過,就會拋出異常,此時就走不到continue部分了,會直接向上層拋異常。
注意,這個重試接口實現了Cloneable,因為每次請求的時候,都要有一個對應的重試對象來記錄當前請求的重試狀態(比如重試了幾次了),正因為有狀態,所以得每次clone一個新的。
Retryer retryer = this.retryer.clone();
默認情況下,不做任何配置,都是不重試的,此時的retryer類型為一個內部類,意思是NEVER_RETRY:
這里再拓展一點,什么情況下,Feign會拋出RetryableException呢?
其實就是在執行上圖的正常執行部分,遇到了java.io.IOException的時候,就會拋這種RetryableException。
static FeignException errorExecuting(Request request, IOException cause) {
return new RetryableException(
-1,
format("%s executing %s %s", cause.getMessage(), request.httpMethod(), request.url()),
request.httpMethod(),
cause,
null, request);
}
還有一種情況是啥呢?是服務端返回的http header中包含了Retry-After這個header的時候:
這個header一般是在503的時候返回:
根據模版創建請求
大家看看我們的接口,用了path variable,所以,在真正進行請求前,必須先完成一些準備工作,比如把path variable替換為真實的值:
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);
另外,大家看代碼,還有個參數傳給下層:
這個就是控制請求超時參數的。這個options從哪里來呢,從我們的傳參來。我們可以在方法中加一個參數:
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message, Request.Options options);
這個的優先級,我覺得應該是最高的。
這樣就能支持,每個接口用不一樣的超時時間。
executeAndDecode概覽
生成絕對路徑請求
先說說1處:
Request targetRequest(RequestTemplate template) {
// 使用請求攔截器
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
請求攔截器的類型:
public interface RequestInterceptor {
/**
* Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
*/
void apply(RequestTemplate template);
}
這里是對模版進行修改,我看注釋,有如下場景(增加全局的header):
接下來,再看看模版如何轉化為請求:
return target.apply(template);
結果,其實也沒干啥,就是模版里只有接口的相對路徑,此處要拼接為完整路徑:
client.execute接口
實際執行請求,靠client對象,它的默認類型為:
它是自動裝配的:
它內部包裹了實際發http請求的庫,上面的代碼中,默認用的是feign自帶的(位于feign-core依賴):
new Client.Default(null, null)
比如,假設我們想用httpclient (在classpath中包含),就會觸發自動裝配:
也支持okhttp(feign.okhttp.enabled為true):
這里看看FeignBlockingLoadBalancerClient的幾個構造參數:
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
第一個是上面提到的http客戶端,第二個、第三個呢,其實是負責負載均衡的客戶端(不只是要負責客戶端負載均衡算法,還要負責從nacos這些地方獲取服務對應的實例)
client獲取服務實例
這個execute方法實際有兩部分,一部分是如下,根據service名稱,獲取服務實例(包含了真實的ip、端口),再一部分才是對服務實例發起請求。
下面的1處,是獲取一些listener,然后通知listener,我們已經開始請求了,這里的listener的類型是:
org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle
包含了好些個生命周期方法,如onStart方法:
void onStart(Request<RC> request);
然后是2處,利用loadBalancerClient,根據服務名,如我們這里的echo-service-provider,獲取到一個實例(類型為org.springframework.cloud.client.ServiceInstance)。
3處會判斷,如果沒獲取到實例,此時就會報503了,服務實例不存在。
這里面,獲取實例的部分,比較復雜,我們得單獨開一篇來講。
發起真實請求
根據服務實例,組裝真實的url進行請求。
這個等負載均衡部分寫完了,再講解這部分,這塊的邏輯也還好,無非是對httpclient這些的封裝。
總結
我們把整體的feign調用的脈絡梳理了一遍,下篇繼續loadbalancer的部分,那里也是有點坑的。
用講師的話來結個尾:啥也不是,散會!
總結
以上是生活随笔為你收集整理的Feign源码解析4:调用过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 作为电视机未来,荣耀智慧屏的价格会不会很
- 下一篇: 2645. 构造有效字符串的最少插入数