SpringCloud整合Feign基本使用及源码分析-02

又是美好的一天呀~
个人博客地址: huanghong.top

服务调用源码分析

FeignClient实例为动态代理创建的对象,当进行服务调用FeignClient的接口方法就会被FeignInvocationHandler的invoke方法拦截。

//feign.ReflectiveFeign.FeignInvocationHandler#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //equals、hashCode、toString方法调用的处理
  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();
  }
  //根据调用方法名获取对应MethodHandler来执行请求
  return dispatch.get(method).invoke(args);
}

invoke

//feign.SynchronousMethodHandler#invoke
public Object invoke(Object[] argv) throws Throwable {
  //根据方法入参及接口方法对应处理器属性构建一个Http请求模版
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  //获取请求相关超时参数
  Options options = findOptions(argv);
  //获取请求重试器
  Retryer retryer = this.retryer.clone();
  //循环请求
  while (true) {
    try {
      //执行请求并解码响应内容
      return executeAndDecode(template, options);
    } catch (RetryableException e) {
      //503响应值会触发返回ErrorDecoder
      //请求抛出RetryableException异常才会触发重试,非RetryableException会抛出异常不进行重试
      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;
    }
  }
}

executeAndDecode

//feign.SynchronousMethodHandler#executeAndDecode
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  //request进行拦截处理,创建一个全新的request对象
  Request request = targetRequest(template);

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

  Response response;
  long start = System.nanoTime();
  try {
    //执行http请求
    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);

  //decoder不为空则对响应内容进行解码直接返回
  if (decoder != null)
    return decoder.decode(response, metadata.returnType());

  //decoder为空,则使用feign.optionals.OptionalDecoder进行异步解码返回
  CompletableFuture<Object> resultFuture = new CompletableFuture<>();
  asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
      metadata.returnType(),
      elapsedTime);

  try {
    if (!resultFuture.isDone())
      throw new IllegalStateException("Response handling not done");

    return resultFuture.join();
  } catch (CompletionException e) {
    Throwable cause = e.getCause();
    if (cause != null)
      throw cause;
    throw e;
  }
}

targetRequest

RequestInterceptor是Feign提供的扩展点,当实际开发过程中远程服务调用有设置请求头相关参数时,由于Feign的远程调用会构建一个全新的Request对象,导致原请求相关信息会被移除,这个时候可以通过RequestInterceptor来手动添加原请求相关参数,避免请求信息丢失的情况。

Request targetRequest(RequestTemplate template) {
  //request扩展,对请求进行拦截处理
  for (RequestInterceptor interceptor : requestInterceptors) {
    interceptor.apply(template);
  }
  //获取全新的Request对象
  return target.apply(template);
}

根据扩展点进行功能扩展

解决Feign远程调用请求头丢失问题

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    requestTemplate.header(name, values);
                }
            }
        }
    }
}

Feign异步情况丢失上下文问题

//问题主要出在 RequestContextHolder.getRequestAttributes()
public static RequestAttributes getRequestAttributes() {
    //它是从requestAttributesHolder这里面取出来的
    RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
    if (attributes == null) {
        attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
    }

    return attributes;
}

//requestAttributesHolder是一个NamedThreadLocal对象
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");

//NamedThreadLocal继承自ThreadLocal
//而ThreadLocal是一个线程局部变量,在不同线程之间是独立的所以我们获取不到原先主线程的请求属性,即给请求头添加cookie失败
public class NamedThreadLocal<T> extends ThreadLocal<T> {
    private final String name;

    public NamedThreadLocal(String name) {
        Assert.hasText(name, "Name must not be empty");
        this.name = name;
    }

    public String toString() {
        return this.name;
    }
}

//解决方案
 // 1.获取之前的请求头数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //2.每一个线程都共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //远程查询
            ....
}, executor);

感谢阅读完本篇文章!!!
个人博客地址: huanghong.top

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢正常冰的冰美式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值