• Spring: Feign原理解析



    简介

    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);
        }
    
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    可以看到在方法 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 转换为  Map 方便调用
        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;
      }
    
    ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    总体流程就是在方法 T newInstance(Target target) 生成一个含有 FeignInvocationHandler 的代理对象,FeignInvocationHandler 对象会持有 Map 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;
        }
      }
    
    ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

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

    总结

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

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

  • 相关阅读:
    【React】手把手学习React - 元素渲染
    使用Vercel托管python后端API——引包引环境,手把手详细教程
    力扣-279题 完全平方数(C++)- 完全背包
    Mysql基本知识篇
    paddle 静态图自定义Python算子
    机器学习(13)---降维实例
    Java编程学习-数据库连接池
    ftp安装流程
    离子交换树脂去除镍,除镍设备及工艺
    机器学习之算法部分(算法篇1)
  • 原文地址:https://blog.csdn.net/zhanggqianglovec/article/details/126140676