龙空技术网

Spring Cloud OpenFeign源码请求原理解析

爱好编程的程序员老徐 294

前言:

此刻小伙伴们对“设置请求头信息的方法”大约比较关怀,姐妹们都想要分析一些“设置请求头信息的方法”的相关知识。那么小编同时在网摘上汇集了一些关于“设置请求头信息的方法””的相关知识,希望同学们能喜欢,大家一起来学习一下吧!

前言:

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

源码分析:

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

@Overridepublic 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方法,该方法代码如下:

@Overridepublic 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个截图如下:

HttpMessageConverter类

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

@FeignClient(value = "exercise1")public interface TestFeign {    @GetMapping(value = "/testFeign1")    @ResponseBody    public Car testFeign();}

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

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;}long nextMaxInterval() {  long interval = (long) (period * Math.pow(1.5, attempt - 1));  return interval > maxPeriod ? maxPeriod : 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次过后还是请求不到会抛出异常。

我是爱编程的程序员老徐,如果大家喜欢我的文章请点赞关注,我会持续更新源码的解析,我们下期见。

标签: #设置请求头信息的方法