Feign源码分析(三) - Client调用

Feign源码分析(三) - Client调用,第1张

Feign源码分析(三) - Client调用

@Author:zxw
@Email:[email protected]


目录

    Feign源码分析(一) - 初探FeignFeign源码分析(二) - builder构建​
1.前言

通过前面的文章,已经分析清除了Feign代理类的生成流程。接下来就是看远程调用发起的流程Feign是如何实现的,代码还是跟之前一样,通过connect方法获取到代理对象后,直接调用Feign接口repo

@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");
2.源码分析

前面已经了解到Feign是使用的java接口代理的方式为我们生成了代理对象FeignInvocationHandler​

2.1 FeignInvocationHandler

对于java的接口代理,我们只需实现接口InvocationHandler即可

static class FeignInvocationHandler implements InvocationHandler

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

private String url;
private Class type;

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

 private final Target target;

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

 private final Map 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 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 {    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 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.总结

在本篇文章中,主要涉及到两个对象就是FeignInvocationHandler和SynchronousMethodHandler。一个是我们接口的代理类,一个则包含了远程请求调用的具体逻辑。

到这里整个Feign的源码就分析的差不多了,总得来说就是解析类上的注解,生成对应的请求信息,然后通过接口生成代理对象并执行远程调用方法。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5716865.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)