@Author:zxw
@Email:502513206@qq.com


目录

  1. Feign源码分析(一) - 初探Feign
  2. Feign源码分析(二) - builder构建

    1.前言

    通过前面的文章,已经分析清除了Feign代理类的生成流程。接下来就是看远程调用发起的流程Feign是如何实现的,代码还是跟之前一样,通过connect方法获取到代理对象后,直接调用Feign接口repo ```java @RequestLine(“GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20”) List repo(@Param(“owner”) String owner, @Param(“repo”) String repo);

// ——— Gitee connect = Gitee.connect(); List star = connect.repo(“xiaowei_zxw”, “JSDX-JwSystem”);

  1. <a name="Joj0G"></a>
  2. # 2.源码分析
  3. 前面已经了解到Feign是使用的java接口代理的方式为我们生成了代理对象`FeignInvocationHandler`
  4. <a name="ZiBrs"></a>
  5. ## 2.1 FeignInvocationHandler
  6. 对于java的接口代理,我们只需实现接口`InvocationHandler`即可
  7. ```java
  8. static class FeignInvocationHandler implements InvocationHandler

要发起代理调用首先我们得有远程地址的url,以及我们接口类的Class

private String url;
private Class<T> type;

这些Feign则是封装在了HardCodedTarget类,那么得到FeignInvocationHandler的第一个元数据则是

 private final Target target;

在生成代理对象之前,Feign为每个方法生成了一个MethodHandler封装了调用的基本信息,那么我们还需要一个Map映射已找到对应的MethodHandler,如下

 private final Map<Method, MethodHandler> dispatch;

以上就是FeignInvocationHandler类中的两个字段,调用只需根据当前方法从Map中拿到对应的MethodHandler即可。

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);

通过Map拿到对象后,调用了MethodHandler的invoke方法。Feign中提供了MethodHandler的实现类SynchronousMethodHandler,接下来看SynchronousMethodHandler的invoke实现

2.2 SynchronousMethodHandler

先来回顾一下MethodHandler的组成。Feign对接口的方法解析时会生成一个MethodMetadata对象

private final MethodMetadata metadata;

在先前分析Feign生成代理类时说过,Feign在解析方法注解后会生成一个Request的模板工厂类,通过该类可以获取RequestTemplate请求模板

private final RequestTemplate.Factory buildTemplateFromArgs;

对于远程调用的返回值则对应了一个解码器

 private final Decoder decoder;

还需要调用的url等基本信息,上面已经提到这些Feign封装在了Target类中

private final Target<?> target;

在Feign中是以方法的维度发起调用,即每个MethodHandler中还封装了Client对象的基本信息

private final Client client;
private final Retryer retryer;
private final Options options;

这边对于MethodHandler的元数据大致就这些。
在回过来看看接口这个方法,在发起调用前的第一步肯定是组装我们的参数了,将param的值填充到对应的{}号

@RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);

通过RequestTemplate.Factory工厂解析后就能得到一个RequestTemplate对象,该对象包含了http请求的模板信息,此时还不是最终请求的Request对象。

RequestTemplate template = buildTemplateFromArgs.create(argv);

得到了请求模板后,还需要经过请求拦截器调用后才能得到真正的Request对象

Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }

最终得到了一个Request对象

Request request = targetRequest(template);

这时我们就可以使用client客户端发起我们的远程请求了。对于Client接口只提供了一个方法就是发起请求,这里也是一个扩展点,可以让我们定制化自己的client对象。

public interface Client {

  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   */
  Response execute(Request request, Options options) throws IOException;
}

如果我们不指定的话,Feign默认使用的就是java本身提供的网络访问对象HttpURLConnection

@Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
 }

至此远程调用就结束了,回头过看这块调用的完成逻辑

     Response response;
    long start = System.nanoTime();
    try {
      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);

在请求成功拿到Response返回值,这里我的解码器就可以出场对Response响应数据进行解码了。如果我们自定义了解码器,则Feign会使用我们的解码器,如果没有则使用Feign默认提供的解码器AsyncResponseHandler

if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

如果我们远程方法执行失败,Feign会捕获RetryableException异常进行重试,默认的重试次数为5。最后整个invoke方法的调用逻辑就如同上面分析的那样。

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;
      }
    }
  }

3.总结

在本篇文章中,主要涉及到两个对象就是FeignInvocationHandlerSynchronousMethodHandler。一个是我们接口的代理类,一个则包含了远程请求调用的具体逻辑。
image.png
到这里整个Feign的源码就分析的差不多了,总得来说就是解析类上的注解,生成对应的请求信息,然后通过接口生成代理对象并执行远程调用方法。