当前位置: 首页 > article >正文

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

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

往下看看~

  • 服务调用源码分析
    • invoke
    • executeAndDecode
    • targetRequest
      • 根据扩展点进行功能扩展

服务调用源码分析

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


http://www.kler.cn/a/7184.html

相关文章:

  • Ubuntu22.04 安装mysql8 无法修改端口及配置的问题 坑啊~~~~
  • RabbitMQ 在 Java 和 Spring Boot 中的应用详解
  • Sql进阶:字段中包含CSV,如何通过Sql解析CSV成多行多列?
  • 工厂模式-工厂方法模式实现
  • 调用门提权
  • OpenTelemetry 赋能DevOps流程的可观测性革命
  • 团体程序设计天梯赛--5分题
  • ThreeJS-平行光物体投影(十九)
  • 画栋雕梁:定制投资体系4——持有、波动与卖出
  • [oeasy]python0125_汉字打印机_点阵式打字机_汉字字形码
  • gpt训练数据-网页版chat软件
  • everyday复习用
  • 科大讯飞日常实习面试
  • 客户反馈终极指南
  • vba:union方法 并集
  • 马云回国,首谈ChatGPT
  • 【电源专题】什么参数能衡量锂电池自放电率
  • ToBeWritten之IoT移动应用漏洞利用(IOS 应用程序分析)
  • 提高运维效率的N的Linux命令
  • 2023-04-04 2016天梯赛决赛练习题L1
  • 一文彻底读懂异地多活
  • ArrayDeque类常用方法
  • 国产ARM+FPGA架构在“能源电力”中的典型应用详解
  • JVM 垃圾回收器
  • 【从零开始学习 UVM】6.8、UVM 激励产生 —— UVM Sequence 仲裁详解
  • 【蓝桥杯】【嵌入式组别】第四节:Systick系统滴答定时器