1.前言
從第一篇和第二篇中,可以看到Feign最終會為每個帶有
@FeignClient
注解的interface生成一個JDK代理對象。那麼在在通過feign進行遠端調用時,一定會走到這個類的
invoke
方法中去。是以接下來我們探究一下
invoke
方法究竟在幹什麼~~
2.FeignInvocationHandler的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// 這個代碼塊中,上面基本都是走不到的,核心就是這個了。
return dispatch.get(method).invoke(args);
}
2.1 那麼這個dispatch是什麼呢?
這是我自己debug出來的
dispatch
中的資訊。其實就是關于接口中每個方法的資訊還有
ReflectiveFeign
所包含的資訊。
2.2 dispatch怎麼來的呢?
其實就是在
ReflectiveFeign
的
newInstance
方法中建構并傳入代理對象的
InvocationHanlder
的一組
Map<Method,MethodHandler>
。
下面是nameToHandler擷取與結構圖
這裡其實我們就可以看出,每個Feign的JDK代理對象的InvocationHanlder中都包含一個
SynchronousMethodHandler
的集合。我們想要擷取接口方法的任何資訊都可以通過
SynchronousMethodHandler
來完成。
那麼我們就能知道
dispatch.get(method)
擷取的就是一個
SynchronousMethodHandler
。
接下來就是
SynchronousMethodHandler
的
invoke
調用了。
3.SynchronousMethodHandler的invoke方法
public Object invoke(Object[] argv) throws Throwable {
// 這個就是通過參數和方法的資訊建構請求連結和請求參數資訊
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// (核心) 執行請求并解碼結果
return executeAndDecode(template);
} catch (RetryableException e) {
try {
// 重試間隔計算與睡眠(sleep),如果超過次數,直接抛出異常
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
3.1 RequestTemplate http請求構模組化闆
RequestTemplate中包含頭資訊,queryString參數,請求方法,請求體,uri資訊等等。這基本都是發送http請求需要的基礎資訊。
3.2 executeAndDecode 執行請求并解碼傳回結果
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 将RequestTemplate儲存的請求資訊轉化為Request
// 對了SynchronousMethodHandler中的攔截器也得加到request中
// 其實這裡的攔截器并不是http請求的攔截器,這個攔截器的作用其實是對RequestTemplate的
// 畢竟這個Request是通過RequestTemplate建立的
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 通過LoadBalancerFeignClient來繼續往下執行
response = client.execute(request, options);
} catch (IOException e) {
// 打日志抛異常
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
if (Response.class == metadata.returnType()) {
// 傳回結果直接為Reponse的
if (response.body() == null) {
return response;
}
// 對于響應結果為空和超過最大響應長度的,不進行轉化處理
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
// 一般正常傳回都會到這裡
if (void.class == metadata.returnType()) {
//針對空傳回
return null;
} else {
// 解碼器解碼
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
上面我們需要關注的其實就兩個點:
-
,将請求交給response = client.execute(request, options)
進行處理LoadBalancerFeignClient
-
,對傳回結果進行解碼Object result = decode(response)
3.3 LoadBalancerFeignClient的execute做了啥?
- 拿出服務名,清理url位址
- 建構
FeignLoadBalancer.RibbonRequest
- 擷取
去執行FeignLoadBalancer
executeWithLoadBalancer
那現在主要研究一下
executeWithLoadBalancer
這個方法,這個方法很關鍵,而且也比較比較複雜。
FeignLoadBalancer
的
executeWithLoadBalancer
方法在其父類
AbstractLoadBalancerAwareClient
中。
3.4 AbstractLoadBalancerAwareClient的executeWithLoadBalancer來自rxjava的降維打擊
其實在真正看到這裡之前,我一直都以為feign走的是ribbon+restTemplate的那套邏輯。直到看到了
AbstractLoadBalancerAwareClient
的
executeWithLoadBalancer
方法。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 建構一個LoadBalancerCommand,将LoadBalancerContext,request,requestConfig都丢到
// 這個對象中,而LoadBalancerContext中就包含了ILoadBalancer,也就是
// DyanmicServerListLoadBalancer
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
// 傳遞進去一個匿名對象
new ServerOperation<T>() {
@Override
// 這個是在LoadBalancerCommand中會被調用
// 在LoadBalancerCommand中,會通過ILoadBalancer去選擇一個服務
// 然後傳遞到這個方法中去
// 是以feign與ribbon的結合,以及ribbon的服務選擇在
// LoadBalancerCommand中
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
在這個方法和
LoadBalancerCommand
的
submit
方法中,存在一些Rxjava的類的使用,這個我還沒有研究過,是以暫時隻能先跳過,挑重點看了。
簡單看一下
LoadBalancerCommand
方法的
submit
方法,看看如何選擇服務的。
深入看一下
selectServer
方法
繼續跟蹤
getServerFromLoadBalancer
ok,到此結束。
ribbon按照自己的政策選擇了服務。
3.5 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 将通過ribbon擷取的服務進行http請求發送
具體的調用鍊路為:
- AbstractLoadBalancerAwareClient.this.execute
- org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
- feign.Client.Default#execute
- feign.Client.Default#convertAndSend 最後直接用HttpURLConnection 直接撸了消息發送。這也是我始料未及的。
3.6 補個漏 解碼器解碼
4.小結
今天看了在進行遠端Feign調用時,會通過注入的JDK代理對象調用
FeignInvocationHandler
的
invoke
方法,然後一路最終到進行http請求發送。
現在簡單描述一下整個流程和核心元件:
- feign請求首先進入
的invoke方法,invoke進行方法處理器的選擇FeignInvocationHandler
-
通過invoke方法選擇出對應方法的FeignInvocationHandler
進行處理SynchronousMethodHandler
-
建構請求模闆SynchronousMethodHandler
和請求RequestTemplate
Request
-
将建構的請求交給SynchronousMethodHandler
LoadBalancerFeignClient
-
建構LoadBalancerFeignClient
,将請求交給RibbonRequest
進行處理FeignLoadBalancer
-
建構FeignLoadBalancer
LoadBalancerCommand
-
通過LoadBalancerCommand
方法去調用submit
去擷取serverILoadBalancer.chooseServer(key)
-
将擷取的server交給LoadBalancerCommand
FeignLoadBalancer
-
将server和請求交給FeignLoadBalancer
Client
-
通過Client
去建立連接配接并發送請求HttpURLConnection
至此,feign的請求的發送鍊路就算看完了。
其實裡面還有關于Encoder如果組裝頭資訊,queryString,消息體;
Contract如果将springmvc的注解進行解析并生成對應的url,這些暫時都還沒去深入的看。
後續看看有沒有探讨和深入的價值。