源码解析: Feign MethodHandler

作用

MethodHandler从字面意思理解这是个方法管理器

 /**
   * Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
   * single method.
   */
  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

如注释所言,如果熟悉java.lang.reflect.InvocationHandler#invoke。大概就能明白。这是为了执行一个方法而预备的组件。
那么里面就应该包含了我们通过执行HTTP请求从而完成一个预定义的方法的逻辑。

SynchronousMethodHandler

这个类是Feign对MethodHandler的默认实现。参数太多,多以通过Factory工厂类去创建。

    Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors,
            Logger logger, Logger.Level logLevel, boolean decode404) {
      this.client = checkNotNull(client, "client");
      this.retryer = checkNotNull(retryer, "retryer");
      this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors");
      this.logger = checkNotNull(logger, "logger");
      this.logLevel = checkNotNull(logLevel, "logLevel");
      this.decode404 = decode404;
    }

这里需要为其提供6个属性,既图中红色部分的组件。大致能猜出它们的作用。

  1. Client
    代表的是真正去执行HTTP请求的组件。他可以是OkHttpClient,也可以是Apache的HttpClient。草草观察Feign的默认实现Client.Default类可以看出其逻辑就是通过传入的Request相关参数,打开一个HttpURLConnection,执行完毕后,转换结果为Response对象,关闭连接。
  2. Retryer
    当我们的http请求通过client执行后,如果出现RetryableException。
    由Retryer这个组件负责决定是否按规定逻辑重新执行,或者抛出异常,中断执行。
    因为每个客户端发起的HTTP请求执行的情况各个不同,所以我们必须为每一个的http请求单独生成一个Retryer的实例,保存相关信息。这里使用了Clone,实现原型模式。
  3. RequestInterceptor
    为每个请求执行前加入拦截器的逻辑
  4. Logger
    Logger虚类自己实现了如下几个方法,你可以从名字和代码知道他们大概是在什么时候会被调用,用来记录日志。
    logRequest
    logRetry
    logAndRebufferResponse
    logIOException
    另外留下了一个log方法给子类自己去实现记录日志的场景逻辑。
  5. decode404
    这是个布尔值,是否反编码404?如果没看使用说明你也能猜到对于执行HTTP请求后得到404的情况,Feign提供了特殊的选项,其实就是就是抛出异常还是返回null的区别。

总结以上几个组件的作用。他们在一起的作用是“执行HTTP请求”。所以我们传给SynchronousMethodHandler.Factory的改造参数是告诉怎么去“执行HTTP请求”。

invoke方法

这个invoke方法就是我们真正调用某个接口的某个方法的时候,去执行的完整逻辑,只不过最后这个逻辑会被嵌入动态代理生成的类中而已。准备这个方法所需要的组件简直就是山路十八弯,但是这些组件最后被使用,就这么点 代码……

@Override
  public Object invoke(Object[] argv) throws Throwable {
    //这个RequestTemplate可以在每次调用时创建一个Request。
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    //Retryer对于每次调用都需要一个单独的重试计数器。
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }
Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).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);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
                response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        return decode(response);
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,026评论 19 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,742评论 25 709
  • 转载请标明出处:本文出自方志朋的博客 什么是Feign Feign是受到Retrofit,JAXRS-2.0和We...
    方志朋阅读 1,641评论 1 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,785评论 18 399
  • 这几天好累,昨天刚写好标题,睡着了,太困了。今天羽菲上托辅,托辅老师要讲语文新课堂,她忘带了,说是自己回学...
    羽菲傲霖阅读 131评论 0 0