spring cloud feign调用原理_vip解析的原理

spring cloud feign调用原理_vip解析的原理Feign是⼀个HTTP请求的轻量级客户端框架。通过接口+注解的方式发起HTTP请求调用,面向接口编程,而不是像Java中通过封装HTTP请求报文的方式直接调用。服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。让我们更加便捷和优雅的去调⽤基于HTTP的API,被⼴泛应⽤在SpringCloud的解决⽅案中。…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用


简介

Feign 是⼀个 HTTP 请求的轻量级客户端框架。通过 接口 + 注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文的方式直接调用。服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求。让我们更加便捷和优雅的去调⽤基于 HTTP 的 API,被⼴泛应⽤在 Spring Cloud 的解决⽅案中。

一、Why?为什么要使用Feign

Feign 的首要目标就是减少 HTTP 调用的复杂性。在微服务调用的场景中,我们调用很多时候都是基于 HTTP 协议的服务,如果服务调用只使用提供 HTTP 调用服务的 HTTP Client 框架(e.g. Apache HttpComponnets、HttpURLConnection OkHttp 等),我们需要关注哪些问题呢?
在这里插入图片描述
相比这些 HTTP 请求框架,Feign 封装了 HTTP 请求调用的流程,而且会强制使用者去养成面向接口编程的习惯(因为 Feign 本身就是要面向接口)。

二、How?使用

1、Feign原生使用

以获取 Feign 的 GitHub 开源项目的 Contributors 为例,原生方式使用 Feign 步骤有如下三步(这里以使用 Gradle 进行依赖管理的项目为例):

第一步: 引入相关依赖:implementation ‘io.github.openfeign:feign-core:11.0’
在项目的 build.gradle 文件的依赖声明处 dependencies 添加该依赖声明即可。

第二步: 声明 HTTP 请求接口
使用 Java 的接口和 Feign 的原生注解 @RequestLine 声明 HTTP 请求接口,从这里就可以看到 Feign 给使用者封装了 HTTP 的调用细节,极大的减少了 HTTP 调用的复杂性,只要定义接口即可。
在这里插入图片描述
第三步: 配置初始化 Feign 客户端
最后一步配置初始化客户端,这一步主要是设置请求地址、编码(Encoder)、解码(Decoder)等。
在这里插入图片描述
通过定义接口,使用注解的方式描述接口的信息,就可以发起接口调用。最后请求结果如下:
在这里插入图片描述

2、结合SpringCloud 使用

同样还是以获取 Feign 的 GitHub 开源项目的 Contributors 为例,结合 Spring Cloud 的使用方式有如下三步:

第一步: 引入相关 starter 依赖:org.springframework.cloud:spring-cloud-starter-openfeign
在项目的 build.gradle 文件的依赖声明处 dependencies 添加该依赖声明即可。

第二步: 在项目的启动类 XXXApplication 上添加 @EnableFeignClients 注解启用 Feign 客户端功能。
在这里插入图片描述
第三步: 创建 HTTP 调用接口,并添加声明 @FeignClient 注解。
最后一步配置初始化客户端,这一步主要是设置请求地址(url)、编码(Encoder)、解码(Decoder)等,与原生使用方式不同的是,现在我们是通过 @FeignClient 注解配置的 Feign 客户端属性,同时请求的 URL 也是使用的 Spring MVC 提供的注解。
在这里插入图片描述

测试类如下所示:
在这里插入图片描述

运行结果如下:
在这里插入图片描述
可以看到这里是通过 @Autowired 注入刚刚定义的接口的,然后就可以直接使用其来发起 HTTP 请求了,使用是不是很方便、简洁。

三、原理分析

从上面第一个原生使用的例子可以看到,只是定了接口并没有具体的实现类,但是却可以在测试类中直接调用接口的方法来完成接口的调用,我们知道在 Java 里面接口是无法直接进行使用的,因此可以大胆猜测是 Feign 在背后默默生成了接口的代理实现类,也可以验证一下,只需在刚刚的测试类 debug 一下看看接口实际使用的是什么实现类:
在这里插入图片描述
从 debug 结果可知,框架生成了接口的代理实现类 HardCodedTarget 的对象 $Proxy14 来完成接口请求调用,和刚刚的猜测一致。Feign 主要是封装了 HTTP 请求调用,其整体架构如下:
在这里插入图片描述
测试类代码里面只在 GitHub github = Feign.builder().target(GitHub.class, “https://api.github.com”); 用到了 Feign 框架的功能,所以我们选择从这里来深入源码,点击进入发现是 Feign 抽象类提供的方法,同样我们知道抽象类也是无法进行初始化的,所以肯定是有子类的,如果你刚刚有仔细观察上面的 debug 代码的话,可以发现有一个 ReflectiveFeign 类,这个类就是抽象类 Feign 的子类了。抽象类 feign.Feign 的部分源码如下:

public abstract class Feign { 
   
    
  ...  

  public static Builder builder() { 
   
    return new Builder();
  }

  public abstract <T> T newInstance(Target<T> target);

  public static class Builder { 
   

    ...

    private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    private Contract contract = new Contract.Default();
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    private boolean forceDecoding = false;
    private List<Capability> capabilities = new ArrayList<>();

    // 设置输入打印日志级别
    public Builder logLevel(Logger.Level logLevel) { 
   
      this.logLevel = logLevel;
      return this;
    }

    // 设置接口方法注解处理器(契约) 
    public Builder contract(Contract contract) { 
   
      this.contract = contract;
      return this;
    }

    // 设置使用的 Client(默认使用 JDK 的 HttpURLConnection)
    public Builder client(Client client) { 
   
      this.client = client;
      return this;
    }

    // 设置重试器
    public Builder retryer(Retryer retryer) { 
   
      this.retryer = retryer;
      return this;
    }

    // 设置请求编码器 
    public Builder encoder(Encoder encoder) { 
   
      this.encoder = encoder;
      return this;
    }

    // 设置响应解码器
    public Builder decoder(Decoder decoder) { 
   
      this.decoder = decoder;
      return this;
    }

    // 设置 404 返回结果解码器
    public Builder decode404() { 
   
      this.decode404 = true;
      return this;
    }

    // 设置错误解码器
    public Builder errorDecoder(ErrorDecoder errorDecoder) { 
   
      this.errorDecoder = errorDecoder;
      return this;
    }

    // 设置请求拦截器
    public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) { 
   
      this.requestInterceptors.clear();
      for (RequestInterceptor requestInterceptor : requestInterceptors) { 
   
        this.requestInterceptors.add(requestInterceptor);
      }
      return this;
    }

    public <T> T target(Class<T> apiType, String url) { 
   
      return target(new HardCodedTarget<T>(apiType, url));
    }

    public <T> T target(Target<T> target) { 
   
      return build().newInstance(target);
    }

  }
}

可以看到在方法 public T target(Class apiType, String url) 中直接创建了 HardCodedTarget 对象出来,这个对象也是上面 debug 看到的对象。再继续深入,就来到了 feign.Feign 的 newInstance(Target target) 的方法了,是个抽象方法,其实现在子类 ReflectiveFeign 中,这个方法就是接口代理实现生成的地方,下面通过源码来看看实现逻辑是怎样的:

public class ReflectiveFeign extends Feign { 
   

  ...  

  private final ParseHandlersByName targetToHandlersByName;
  private final InvocationHandlerFactory factory;
  private final QueryMapEncoder queryMapEncoder;

  ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
      QueryMapEncoder queryMapEncoder) { 
   
    this.targetToHandlersByName = targetToHandlersByName;
    this.factory = factory;
    this.queryMapEncoder = queryMapEncoder;
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T newInstance(Target<T> target) { 
   
    // <类名#方法签名, MethodHandler>,key 是通过 feign.Feign.configKey(Class targetType, Method method) 生成的
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    // 将 Map<String, MethodHandler> 转换为 Map<Method, MethodHandler> 方便调用
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // 默认方法处理器
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) { 
   
      // 跳过 Object 类定于的方法 
      if (method.getDeclaringClass() == Object.class) { 
   
        continue;
      } else if (Util.isDefault(method)) { 
   
        // 默认方法(接口声明的默认方法)使用默认的方法处理器 
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else { 
   
        // 接口正常声明的方法(e.g. GitHub.listContributors(String, String)) 
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }

    // 生成 Feign 封装的 InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 基于 JDK 动态代理生成接口的代理类(e.g. Github 接口)
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] { 
   target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { 
   
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

...

}

总体流程就是在方法 T newInstance(Target target) 生成一个含有 FeignInvocationHandler 的代理对象,FeignInvocationHandler 对象会持有 Map<Method, MethodHandler> map,代理对象调用的时候进入 FeignInvocationHandler#invoke 方法,根据调用的方法来获取对应 MethodHandler,然后再 MethodHandler 完成对方法的处理(处理 HTTP 请求等)。

下面再深入 MethodHandler,看看是如何完成对方法 HTTP 请求处理的,MethodHandler 是一个接口定义在 feign.InvocationHandlerFactory 接口中(P.S. 基础知识点,接口是可以在内部定义内部接口的哦),有两个实现类分别为 DefaultMethodHandler 和 SynchronousMethodHandler,第一个 DefaultMethodHandler 用来处理接口的默认方法,第二个是用来处理正常的接口方法的,一般情况下都是由该类来处理的。

final class SynchronousMethodHandler implements MethodHandler { 
   

  ...

  @Override
  public Object invoke(Object[] argv) throws Throwable { 
   
    // 获取 RequestTemplate 将请求参数封装成请求模板 
    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;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { 
   
    // 从请求模板 RequestTemplate 构造请求参数对象 Request 
    Request request = targetRequest(template);

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

    Response response;
    long start = System.nanoTime();
    try { 
   
      // 通过 client(Apache HttpComponnets、HttpURLConnection OkHttp 等)执行 HTTP 请求调用,默认是 HttpURLConnection 
      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);

    if (decoder != null)
      // 对返回结果进行解码操作
      return decoder.decode(response, metadata.returnType());

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

...

}

至此,Feign 的核心实现流程介绍完毕,从代码上看 feign.SynchronousMethodHandler 的操作相对比较简单,主要是通过 client 完成请求,对响应进行解码以及异常处理操作,整体流程如下:
在这里插入图片描述

总结

Feign 通过给我们定义的目标接口(比如例子中的 GitHub)生成一个 HardCodedTarget 类型的代理对象,由 JDK 动态代理实现,生成代理的时候会根据注解来生成一个对应的 Map<Method, MethodHandler>,这个 Map 被 InvocationHandler 持有,接口方法调用的时候,进入 InvocationHandler 的 invoke 方法(为什么会进入这里?JDK 动态代理的基础知识)。

然后根据调用的方法从 Map<Method, MethodHandler> 获取对应的 MethodHandler,然后通过 MethodHandler 根据指定的 client 来完成对应处理, MethodHandler 中的实现类 DefaultMethodHandler 处理默认方法(接口的默认方法)的请求处理的,SynchronousMethodHandler 实现类是完成其它方法的 HTTP 请求的实现,这就是 Feign 的主要核心流程。以上是 Feign 框架实现的核心流程介绍。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/188228.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(1)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号