您的当前位置:首页正文

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 + ")");
    }
  }
}

上述事例中展示了一种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 + ")");
    }
  }
}

深入探究

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

从上面的事例中我们可以看到,最终是通过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;
  }

首先来看第一行, Map<String, MethodHandler> 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;
    }

这里有一个非常重要的类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);

这里的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);
    }
  }

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

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

总结

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

因篇幅问题不能全部显示,请点此查看更多更全内容

Top