@Author:zxw
@Email:502513206@qq.com


目录

  1. Feign源码分析(一) - 初探Feign

    1.前言

    通过上篇文章,我们得知了Feign类中主要的元数据,接下来就看下Feign是如何为我们生成代理类的。可以看到调用target方法时,传入了Class参数Gitee.Class,那么Feign是通过接口代理的方式来生成实现类的,

    1. interface Gitee {
    2. @RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
    3. List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);
    4. default List<Stargazers> stargazers(String owner, String repo) {
    5. return repo(owner, repo);
    6. }
    7. static Gitee connect() {
    8. final Decoder decoder = new GsonDecoder();
    9. final Encoder encoder = new GsonEncoder();
    10. return Feign.builder()
    11. .encoder(encoder)
    12. .decoder(decoder)
    13. .logger(new Logger.ErrorLogger())
    14. .logLevel(Logger.Level.BASIC)
    15. .requestInterceptor(template -> {
    16. // template.header(
    17. // "Authorization",
    18. // "token 383f1c1b474d8f05a21e7964976ab0d403fee071");
    19. })
    20. .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
    21. .target(Gitee.class, "https://gitee.com");
    22. }
    23. }

    2.源码分析

    Builder模式中调用build()方法时,会生成实例化对象。上述代码在build时生成的不是Feign类,而是Feign的子类ReflectiveFeign
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    在代码中我们只定义了接口,而接口的实现则是Feign帮我们生成,所以在build结束后,还会调用方法为我们生成代理类,如下

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

    在生成实现类之前,我们还需要对接口上的注解进行解析

    @RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
    List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);
    

    先前讲解Feign元数据时说过,对于的注解的解析是通过Contract类来实现的,可以看到对于每个方法都会生成一个MethodMetadata对象,接下来对MethodMetadata对象进行分析

    List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
    

    2.1 MethodMetadata

    对于这个类名就能得知该类存储的是方法的元数据,那么一定有一个Method字段指向方法,有了方法的引用当然还得有类本身的引用以及返回值类型等等

    private transient Method method;
    private transient Class<?> targetType;
    private transient Type returnType;
    

    还需要一个key用来作为方法的名称,就像mybatis为接口生成映射的key一样,类名#方法名

    private String configKey;
    

    以上是method的基本属性,在编写feign的接口时还可以指定参数替换等,比如在参数处使用@param注解标注替换的属性

    @RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
    List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);
    

    @RequestLine注解中{}内对应的值会被映射为索引位,从0开始。所以应当还有一个用来存储索引位对应的参数名称的映射关系

    private final Map<Integer, Collection<String>> indexToName =
       new LinkedHashMap<Integer, Collection<String>>();
    

    对于MethodMetadata目前用到的元数据就介绍这么多。接下来看Feign解析注解的具体流程

    2.2 Contract方法解析

    在我们生成Feign类时,会自动生成一个Contract的实现类来解析类中方法的Http请求信息

    private Contract contract = new Contract.Default();
    

    对于其类的继承图关系如下
    image.png
    那么我们先来看看BaseContract的具体内容,Contract接口只提供了一个方法,就是根据Class来解析和验证元数据,该方法有公共的BaseContract提供实现

    List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType);
    

    此外BaseContract还有3个抽象方法让子类实现 ```java /**

    • 解析类上的注解 */ protected abstract void processAnnotationOnClass(MethodMetadata data, Class<?> clz);

/**

  • 解析方法上的注解 */ protected abstract void processAnnotationOnMethod(MethodMetadata data,
                                                   Annotation annotation,
                                                   Method method);
    

/**

  • 解析参数上的注解 */ protected abstract boolean processAnnotationsOnParameter(MethodMetadata data,
                                                          Annotation[] annotations,
                                                          int paramIndex);
    
    通过这几个方法,对于Contract接口的职责大概已经了解。首先传入接口Class,调用`parseAndValidateMetadata`获取基本信息,然后依次调用上面3个方法解析类、方法、参数上的注解。通过这套解析流程,我们也能定义自己的注解进行解析了。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/12949707/1643477916856-ec3378a4-8141-4452-9908-9e4ad287c6fb.png#clientId=ub9635a25-4893-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=735&id=u5468ef92&margin=%5Bobject%20Object%5D&name=image.png&originHeight=735&originWidth=639&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25924&status=done&style=none&taskId=u4ebd8921-5e1c-4370-b4c1-07875be72d1&title=&width=639)<br />具体的方法调用如下,可以看到最终生成了一个List<MethodMetadata>
    ```java
    final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
       for (final Method method : targetType.getMethods()) {
         if (method.getDeclaringClass() == Object.class ||
             (method.getModifiers() & Modifier.STATIC) != 0 ||
             Util.isDefault(method)) {
           continue;
         }
         final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
         checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",
             metadata.configKey());
         result.put(metadata.configKey(), metadata);
       }
    return new ArrayList<>(result.values());
    

    2.3 ParseHandlersByName

    上面我们已经获取到了一个MethodMetadata列表,这里仅仅包含了方法的一些基本信息。还未涉及到编码器,所以Feign提供了一个RequestTemplate.Factory工厂类,通过请求参数的类型,Feign为我们提供不同的模板工厂类。
    if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
           buildTemplate =
               new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
         } else if (md.bodyIndex() != null) {
           buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
         } else {
           buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
         }
    
    前面都是封装了方法的基本参数,我们在发起调用时还需要Client客户端等参数,Feign提供了MethodHandler作为方法请求的执行器。 ```java MethodHandler -> SynchronousMethodHandler

public MethodHandler create(Target<?> target, MethodMetadata md, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) { return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, decode404, closeAfterDecode, propagationPolicy, forceDecoding); }

那么这个`ParseHandlersByName`类其实就类似于一个工具类,里面调用了contract接口的解析获得了`MethodMetadata`列表,然后在将方法包装成`MethodHandler`,最后根据configKey返回一个Map
```java
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

2.4 FeignInvocationHandler

前面已经完成了方法的解析了,那么最后就是为我们的Feign接口生成代理类,在这里Feign提供了一个InvocationHandlerFactory工厂来生成

static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

通过源码可以看到,Feign是使用jdk的代理方式来生成一个代理对象FeignInvocationHandler

static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }
}

3.总结

对于Feign类build的流程已经分析完了,接下来在回顾一下构建的过程
image.png
其中使用到的类有如下

  1. Feign -> ReflectiveFeign:builder类
  2. ParseHandlersByName:解析方法上的注解并生成Map映射关系
  3. MethodMetadata:方法解析后对象
  4. BuildTemplateByResolvingArgs:包装了MethodMetadata以及编码器等
  5. SynchronousMethodHandler:包装了MethodMetadata、Template以及解码器等调用参数
  6. InvocationHandlerFactory:Feign的代理工厂类
  7. FeignInvocationHandler:Feign为接口生成的代理对象

最后贴上newInstance的源码

public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

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