SpringCloud.Honxton 版本 OpenFeign原理上篇

SpringCloud.Honxton 版本 OpenFeign原理

前置說明

好久沒寫博客了, 突然想起自己的springcloud系列文章就講了配置中心,註冊中心,負載均衡和熔斷。那麼今天就來分析一下openfegin 的原理,後續會分析gateway的原理。

我個人的習慣,在使用springcloud 的組件時,會先去用一下原生依賴的使用方法,因爲在springboot的自動裝配下讓使用變得簡單,但使用也更加機械化,導致我們會無法看透到底在用一個什麼東西。 所以我們先單獨分析一下openfeign的使用以及其本身的原理,然後再來分析springcloud如何進行的整合(下篇)。

openfegin是由neflix公司開源出來,再迭代了數個版本之後徹底改名爲openfeign,在被springcloud整合之後也是十分的受大家的歡迎。

如何使用

public interface HelloOpenfeign {

       @RequestLine("GET /feign?feign={feign}")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    String feign(@Param("feign") String feign);

    @RequestLine("POST /feignPost")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    String feignPost(@Param("feign") String feign);

    @RequestLine("POST /feignJson")
    @Headers("Content-Type: application/json")
    String feignForm(@Param("feign") String feign);

    public static void main(String[] args) {
        HelloOpenfeign client = Feign.builder().target(HelloOpenfeign.class, "http://localhost:12001");
        String result = client.feign("YourBatman");
        System.out.println(result);
    }
}


哎,是不是沒有想到,使用方法竟然如此簡單, 其實就是解析接口所有的方法,根據你配置的註解完成http參數的拼裝,然後給目的地的ip:port就可以了。最後獲取動態代理對象,那麼就像調用本地方法一樣調用http請求了。

openfeign原理

前面其實已經講了內部原理的大致流程, 也就是解析接口中的方法, 然後生成代理對象; 在真正調用時,解析傳遞的參數,然後封裝request,最後進行真正遠程調用, 最後對返回的結果解析封裝成接口方法的返回類。

下面開始結合源碼進行關鍵點的分析。

入口方法就是 feign.Feign.Builder#target(java.lang.Class, java.lang.String)

public static class Builder {
... 省略其他方法

 public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

    public Feign build() {
    // 用於創建真正執行的handler 的工廠
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
    // 接口方法解析器          
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
              // build模式創建的Feign接口的實現類
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }


}

然後來看一下ReflectiveFeign

public class ReflectiveFeign extends Feign {

@Override
  public <T> T newInstance(Target<T> target) {
  // 用 接口方法解析器解析所有的方法, 產生MethodHandler抽象
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
   //  所有的方法處理器
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // 不是遠程調用的接口方法處理器
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	//遍歷所有的方法
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 動態代理執行器
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 代理對象生成
    T proxy = (T)  Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy); // 綁定反射(其實不是,但是可以理解爲反射)的實例
    }
    return proxy;
  }


}

然後簡單分析一下參數解析的過程, 主要是 ParseHandlersByName這個類


 public Map<String, MethodHandler> apply(Target key) {
      List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
         // 一般的post,有表單數據的生成器
          buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
        // 一般的get, 沒有表單數據的生成器
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        // 這裏是關鍵的創建 SynchronousMethodHandler
        result.put(md.configKey(),
            factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
      }
      return result;
    }

看到這其實openfeign的初始化流程已經很明確, 然後是動態代理的執行過程的分析 具體見FeignInvocationHandler

static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }
@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);
    }
... 省略多餘代碼
}


final class SynchronousMethodHandler implements MethodHandler {

@Override
  public Object invoke(Object[] argv) throws Throwable {
  //根據輸入的參數, 生成對應的請求模板
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    // 重試策略
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
      // 真正的執行遠程調用和解析
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        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;
      }
    }
  }


  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 {
    // 這裏又是經典的策略模式, 也是springcloud集成負載均衡的關鍵點
    // 這裏是進行遠程調用
      response = client.execute(request, options);
    } 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);
      }
      ...
      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);
      }
  }
  ... 省略多餘代碼
}


// 默認的client實現
class Default implements Client {
    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

    @Override
    public Response execute(Request request, Options options) throws IOException {		// 是最簡單的 jdk提供的http連接器
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }
}

基本上feigin的啓動和執行過程都分析到了, 只有一些參數解析的和響應對象解析的沒有分析(這些細節不影響我們宏觀把控feign的流程).

最後給上一副feign 的分層架構設計圖.
在這裏插入圖片描述

總結

整體來說 feign 的使用非常的便捷, 但是不爽的地方在於就我們需要自己調用Feign.builder().target來生成代理對象.不過好在有spring來幫我們做了一些瑣碎的事情來避免我們針對每個接口都調用target方法, 就像Aop的整合一樣, 省去了我們自己生產動態代理對象.
下一篇我就會講述springcloud下openfeign的使用,整合的原理以及自動配置的原理.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章