SpringCloud升級之路2020.0.x版-28.OpenFeign的生命週期-進行呼叫

乾貨滿滿張雜湊發表於2021-10-17

本系列程式碼地址:https://github.com/JoJoTec/spring-cloud-parent

接下來,我們開始分析 OpenFeign 同步環境下的生命週期的第二部分,使用 SynchronousMethodHandler 進行實際呼叫,其流程可以總結為:

  1. 呼叫代理類的方法實際呼叫的是前面一章中生成的 InvocationHandlerinvoke 方法。
  2. 預設實現是查詢 Map<Method, MethodHandler> methodToHandler 找到對應的 MethodHandler 進行呼叫,對於同步 Feign,其實就是 SynchronousMethodHandler
  3. 對於 SynchronousMethodHandler:
  4. 使用前面一章分析建立的建立的請求模板工廠 RequestTemplate.Factory,建立請求模板 RequestTemplate
  5. 讀取 Options 配置
  6. 使用配置的 Retryer 建立新的 Retryer
  7. 執行請求並將響應反序列化 - 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 丟擲
  8. 如果第 4 步丟擲 RetryableException,則使用第三步建立的 Retryer 判斷是否重試,如果需要重試,則重新走第 4 步,否則,丟擲異常。

給出這個流程後,我們來詳細分析

OpenFeign的生命週期-進行呼叫原始碼分析

前面一章的最後,我們已經從原始碼中看到了這一章開頭提到的流程的前兩步,我們直接從第三步開始分析。

SynchronousMethodHandler

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

相關文章