Spring Cloud OpenFeign源码请求原理解析

前言:

上一篇文章说到被FeignClient注解修饰的接口会被加载成FeignClientFactoryBean类型,在Spring实例化时候会调用其getObject方法最后创建一个接口的代理类,然后封装了一个FeignInvocationHandler类,这个类实现了InvocationHandler,由动态原理可知在请求的时候会调用该对象的invoke方法,下面我会详细分析这个方法到底做了些什么事情。

源码分析:

先看看FeignInvocationHandler类的invoke方法,该方法的代码如下:

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

dispatch.get(method)得到的类型是SynchronousMethodHandler,回调用其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;
    }
  }

首先创建了一个RequestTemplate对象,这个对象包含请求头信息以及请求方法等属性,接着获取一个Retryer对象,它是负载在远程接口不可调用时候进行重试的类,稍后我分析这个类的源码,接着一个while的死循环,try代码块中调用executeAndDecode方法,这个方法代码如下:

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 {
    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.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 {
        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);
    }
  } 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());
    }
  }
}

这个方法又调用了targetRequest方法,该方法主要是获取所有的请求拦截器,然后循环遍历调用其apply方法,传入RequestTemplate对象,这个拦截器可以给RequestTemplate对象添加请求头信息,例如我的项目里面加入了一个自定义的拦截器,截图如下:

由我的截图可以看出我添加了一个自定义的请求拦截器,往RequestTemplate对象添加一个头信息,这就是拦截器的作用。

接着在targetRequest方法中创建了一个Request对象,成员属性主要有请求方式是GET/POST、请求的url、头信息、请求体参数,这些参数都来源于RequestTemplate对象。

好了targetRequest方法执行完成以后回到executeAndDecode方法,会调用client.execute(request, options),这个client实现类是LoadBalancerFeignClient,这里面的原理是Ribbon相关的源码,我会在分析Ribbon源码时候进行分析,现在我们只考虑返回Response对象以后如何处理的。

接着判断返回的类型是不是Response类型的,如果是直接返回里面的内容,然后判断是否是响应200的响应码,这里假定是200响应码,会调用decode方法进行解码,这个方法调用层次比较深我就不列出代码了,就说一下这里的逻辑是循环调用HttpMessageConverter类,这个类大概有10个截图如下:

这个类是处理我们返回的类比如我们返回的类型是一个Car对象类型,根据返回的媒体类型(text/html,applicaiton/json等)判断是否能处理这个Car类型,下面是我的一个测试Feign接口是这样定义的:

FeignClient(value = "exercise1")
public interface TestFeign {

    @GetMapping(value = "/testFeign1")
    public Car testFeign();
}

我的接口提供方是这样定义的:

@RestController
public class TestFeignController {

    @RequestMapping("testFeign1")
    public Car testFeign() {
        Car car =new Car("tom",11);
        return car;
    }

}

可以看出接口提供方返回的是json类型,那么response中的媒体类型是application/json,我的Feign接口返回是Car类型,那么根据这两个参数能否判断出哪个HttpMessageConverter能处理吗?经过研究可知MappingJackson2HttpMessageConverter这个类可以处理返回的json数据,他可以把json数据转换为Car对象返回。

好了,decode方法执行完成以后就相当于把远程服务返回的数据封装成为我们Feign接口返回类型的对象,那么正常的逻辑就是执行完成了。

前面所说的都是能正常访问远程服务的情况下,还记得我前面截图的SynchronousMethodHandler的invoke方法中有catch代码块的逻辑吗?假如我们的远程服务挂了,会走到 retryer.continueOrPropagate(e)这段代码,这段代码是一个重试机制,我们看看里面的代码是什么样子的:

public void continueOrPropagate(RetryableException e) {
  if (attempt++ >= maxAttempts) {
    throw e;
  }

  long interval;
  if (e.retryAfter() != null) {
    interval = e.retryAfter().getTime() - currentTimeMillis();
    if (interval > maxPeriod) {
      interval = maxPeriod;
    }
    if (interval < 0) {
      return;
    }
  } else {
    interval = nextMaxInterval();
  }
  try {
    Thread.sleep(interval);
  } catch (InterruptedException ignored) {
    Thread.currentThread().interrupt();
    throw e;
  }
  sleptForMillis += interval;
}

这段代码的逻辑是:

定义一个attempt重试次数的变量,每次都加1,检查是否达到最大重试次数 maxAttempts,如果没有指定最大重试次数,默认是5次,如果达到最大重试次数,则直接抛出 RetryableException 异常。

计算下一次重试的时间间隔 interval:如果 RetryableException 中有设置 retryAfter,则计算 interval 为 retryAfter 时间减去当前时间的毫秒数。如果计算出的 interval 大于 maxPeriod,则将其截断为 maxPeriod。如果 interval 小于 0,则表示不需要进行重试,直接返回。

如果 RetryableException 中没有设置 retryAfter,则调用 nextMaxInterval() 方法计算默认的时间间隔。

使用 Thread.sleep(interval) 方法暂停当前线程,使得程序等待一段时间后再进行重试。如果在等待过程中被中断,则会抛出 InterruptedException 异常,并继续抛出 RetryableException 异常。

总体来说,这段代码的作用是在发生可重试异常时,判断是否需要进行重试,如果需要,计算下一次重试的时间间隔,并在间隔后进行重试。在重试过程中,如果线程被中断,则会继续抛出 RetryableException 异常。

总结:

以上就是我分析的Spring Cloud OpenFeign源码请求原理解析过程,下面我做个总结:

请求到达时候会调用FeignInvocationHandler的invoke方法,创建一个RequestTemplate对象设置请求头参数等信息,这些可能是在请求拦截器中设置的,接着调用远程服务获取响应对象,把响应对象解码成为我们Feign接口返回的类型,当服务不可用时候还可以进行重试机制,默认最大为5次,如果5次过后还是请求不到会抛出异常。

我是程序员老徐,下一节我会分析Spring Cloud Ribbon源码解析,还请大家持续关注我,我们下期见。

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值