SpringCloud升級之路2020.0.x版-28.OpenFeign的生命周期-進行調用
- 2021 年 10 月 17 日
- 筆記
- Lettuce連接池, Spring Cloud
接下來,我們開始分析 OpenFeign 同步環境下的生命周期的第二部分,使用 SynchronousMethodHandler 進行實際調用,其流程可以總結為:
- 調用代理類的方法實際調用的是前面一章中生成的
InvocationHandler
的invoke
方法。 - 默認實現是查詢
Map<Method, MethodHandler> methodToHandler
找到對應的MethodHandler
進行調用,對於同步 Feign,其實就是SynchronousMethodHandler
- 對於
SynchronousMethodHandler
: - 使用前面一章分析創建的創建的請求模板工廠
RequestTemplate.Factory
,創建請求模板RequestTemplate
。 - 讀取 Options 配置
- 使用配置的 Retryer 創建新的 Retryer
- 執行請求並將響應反序列化 – executeAndDecode:
1. 如果配置了 RequestInterceptor,則執行每一個 RequestInterceptor
2. 將請求模板 RequestTemplate 轉化為實際請求 Request
3. 通過 Client 執行 Request
4. 如果響應碼是 2XX,使用 Decoder 解析 Response
5. 如果響應碼是 404,並且在前面一章介紹的配置中配置了 decode404 為 true, 使用 Decoder 解析 Response
6. 對於其他響應碼,使用 errorDecoder 解析,可以自己實現 errorDecoder 拋出 RetryableException 來走入重試邏輯
7. 如果以上步驟拋出 IOException,直接封裝成 RetryableException 拋出 - 如果第 4 步拋出 RetryableException,則使用第三步創建的 Retryer 判斷是否重試,如果需要重試,則重新走第 4 步,否則,拋出異常。
給出這個流程後,我們來詳細分析
OpenFeign的生命周期-進行調用源碼分析
前面一章的最後,我們已經從源碼中看到了這一章開頭提到的流程的前兩步,我們直接從第三步開始分析。
public Object invoke(Object[] argv) throws Throwable {
//使用前面一章分析創建的創建的請求模板工廠 `RequestTemplate.Factory`,創建請求模板 `RequestTemplate`。
RequestTemplate template = buildTemplateFromArgs.create(argv);
//讀取 Options 配置
Options options = findOptions(argv);
//使用配置的 Retryer 創建新的 Retryer
Retryer retryer = this.retryer.clone();
while (true) {
try {
//執行請求並將響應反序列化
return executeAndDecode(template, options);
} catch (RetryableException e) {
//如果拋出 RetryableException,則使用 retryer 判斷是否重試,如果需要重試,則繼續請求即重試,否則,拋出異常。
try {
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;
}
}
}
對於 executeAndDecode 其中的源碼,為了兼容異步 OpenFeign 兼容 CompletableFuture 的特性,做了一些兼容性修改導致代碼比較難以理解,由於我們這裡不關心異步 Feign,所以我們將這塊代碼還原回來,在這裡展示:
這個修改對應的 Issue 和 PullRequest 是:
Request targetRequest(RequestTemplate template) {
//如果配置了 RequestInterceptor,則執行每一個 RequestInterceptor
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
//將請求模板 RequestTemplate 轉化為實際請求 Request
return target.apply(template);
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//通過 Client 執行 Request
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, 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);
}
//如果響應碼是 2XX,使用 Decoder 解析 Response
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()) {
//如果響應碼是 404,並且在前面一章介紹的配置中配置了 decode404 為 true, 使用 Decoder 解析 Response
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
//對於其他響應碼,使用 errorDecoder 解析,可以自己實現 errorDecoder 拋出 RetryableException 來走入重試邏輯
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
//如果拋出 IOException,直接封裝成 RetryableException 拋出
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
static FeignException errorReading(Request request, Response response, IOException cause) {
return new FeignException(
response.status(),
format("%s reading %s %s", cause.getMessage(), request.httpMethod(), request.url()),
request,
cause,
request.body(),
request.headers());
}
這樣,我們就分析完 OpenFeign 的生命周期
我們這一節詳細介紹了 OpenFeign 進行調用的詳細流程。接下來我們將開始介紹,spring-cloud-openfeign 裏面,是如何定製 OpenFeign 的組件並粘合的。
微信搜索「我的編程喵」關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: