• Feign都做了什么


    创建一个公用的httpClient肯定是很多码农都遇到过的任务,Feign提供了一个简单优雅的方式。首先要区别一点,@FeignClient是spring封装的,和原始的Feign在用法上有一定的区别。Sping提供的封装固然用起来简单,但是其中的依赖或许会给我们的工程带来很多麻烦。这里我们把重点放在原始的Feign,看看他是如何为我们创建httpClient的。

    创建一个Feign

    interface GitHub {
    
      @RequestLine("GET /repos/{owner}/{repo}/contributors")
      List<Contributor> contributors(@Param("owner") String owner, 
                                     @Param("repo") String repo);
    }
    
    
    public class MyApp {
      public static void main(String... args) {
    
        GitHub github = Feign.builder()
                             .decoder(new GsonDecoder())
                             .target(GitHub.class, "https://api.github.com");
    
        // 发送请求
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
          System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上述事例中展示了一种Feign的用法,这种指定baseUrl的形式最终会生成一个HardCodedTarget,如果baseUrl不固定,Feign还提供了一种EmptyTarget的实现

    interface GitHubSimple {
    
      @RequestLine("GET /repos/{owner}/{repo}/contributors")
      List<Contributor> contributors(URI baseUri,
      								 @Param("owner") String owner, 
                                     @Param("repo") String repo);
    }
    
    public class MyApp {
      public static void main(String... args) {
    
        GitHub github = Feign.builder()
                             .decoder(new GsonDecoder())
                             .target(Target.EmptyTarget.create(GitHubSimple.class));
    
        // 发送请求
        URI baseURI = new URI("https://api.github.com");
        List<Contributor> contributors = github.contributors(baseURI,"OpenFeign", "feign");
        for (Contributor contributor : contributors) {
          System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    深入探究

    Feign的创建和使用就是这么简单,但是在这简单的背后,Feign.Builder到底做了哪些事情,让我们一步步来看。

    	public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
    
        public Feign build() {
          Client client = Capability.enrich(this.client, capabilities);
          Retryer retryer = Capability.enrich(this.retryer, capabilities);
          List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
              .map(ri -> Capability.enrich(ri, capabilities))
              .collect(Collectors.toList());
          Logger logger = Capability.enrich(this.logger, capabilities);
          Contract contract = Capability.enrich(this.contract, capabilities);
          Options options = Capability.enrich(this.options, capabilities);
          Encoder encoder = Capability.enrich(this.encoder, capabilities);
          Decoder decoder = Capability.enrich(this.decoder, capabilities);
          InvocationHandlerFactory invocationHandlerFactory =
              Capability.enrich(this.invocationHandlerFactory, capabilities);
          QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
    
          SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
              new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                  logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                  errorDecoder, synchronousMethodHandlerFactory);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
    
    • 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

    从上面的事例中我们可以看到,最终是通过Feign.Builder.target()方法来生成的实例,在当前的版本中,返回了一个ReflectiveFeign,最终通过ReflectiveFeign.newInstance生成了代理对象。在构建ReflectiveFeign时需要三个参数

    • ParseHandlersByName
    • InvocationHandlerFactory
    • QueryMapEncode

    我们来看看newInstance里都做了什么

    public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        ...
        InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
            new Class<?>[] {target.type()}, handler);
        return proxy;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    首先来看第一行, Map nameToHandler = targetToHandlersByName.apply(target);,这里创建了一个map,key是方法签名,value则是对应的handler,这里的变量targetToHandlersByName就是ParseHandlersByName,我们来看看它具体是怎么做的

    public Map<String, MethodHandler> apply(Target target) {
          List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
          Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
          for (MethodMetadata md : metadata) {
            BuildTemplateByResolvingArgs buildTemplate;
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
              buildTemplate =
                  new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
            } else if (md.bodyIndex() != null || md.alwaysEncodeBody()) {
              buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
            } else {
              buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
            }
            if (md.isIgnored()) {
              result.put(md.configKey(), args -> {
                throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
              });
            } else {
              result.put(md.configKey(),
                  factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
            }
          }
          return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里有一个非常重要的类MethodMetadata,顾名思义它记录了方法的各类元数据,比方说方法签名,url参数的位置,requestBody参数的位置,方法的返回值等等,这里的内容比较多我们暂时不展开。

    在生成了所有方法的MethodMetadata之后,还会根据MethodMetadata里的信息构建不同的buildTemplate(实际上是一个工厂RequestTemplate.Factory),最终根据方法签名,生成对应的代理(SynchronousMethodHandler)。这里的代理就是最终进行http调用的地方。

    看到这里其实最重要的工作都已经结束了,在最后还需要一个代理,将方法的调用dispatch到正确的SynchronousMethodHandler上。

    InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
            new Class<?>[] {target.type()}, handler);
    
    • 1
    • 2
    • 3

    这里的factory是一个InvocationHandlerFactory.Default()实例

     static final class Default implements InvocationHandlerFactory {
    
        @Override
        public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
          return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ReflectiveFeign.FeignInvocationHandler里就非常简单了,

      @Override
      public 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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    简单来说就是做了一个dispatch的工作。

    总结

    这里限于篇幅的原因其实很多细节没有展开,比如Feign的各类配置,如何生成MethodMetadatabuildTemplate是如何工作的。但是整体的流程已经大致梳理清楚了,fi对于我们自己在工作中构建各种公共类库也是非常有参考意义的。后续我们来看看Spring是如何处理的。

  • 相关阅读:
    工具类Utils
    2022年湖北大学招生简章--成人高等教育高起专、专升本学历提升
    OpenLDAP配置web管理界面PhpLDAPAdmin服务-centos9stream
    AE-如何制作湖面水波纹波动的效果
    MOS管开关电路上下拉电阻的作用
    手撕IP核系列——Xilinx FIR IP核之一
    Vite4TSX前端版本号生成
    设计模式之美 - 如何理解单例模式中的唯一性?
    63. 不同路径 II java解决
    python中的模块与包
  • 原文地址:https://blog.csdn.net/nyzzht123/article/details/128186188