Feign详解4-Contract 源码

目录

1. Feign 参数编码整体流程

2. Contract 方法注解及元信息解析

2.1 processAnnotationOnClass

2.2 processAnnotationOnMethod

2.3 processAnnotationsOnParameter

2.4 MethodMetadata

3. 参数解析成 Request

4. 以后只需要实现自己的 Contract,将对应的注解信息解析成 MethodMetadata,即可完成适配工作。


      前面我们大致分析了一下 Feign 的工作原理,它利用jdk面向接口的动态代理机制完成了接口实现类的创建,那 Feign 到底是如何适配 Feign、JAX-RS 1/2 的 REST 声明式注解,将方法的参数解析为 Http 的请求行、请求头、请求体呢?这里就不得不提 Contract这个接口。

1. Feign 参数编码整体流程

总结: 前两步是 Feign 代理生成阶段,解析方法参数及注解元信息。后三步是调用阶段,将方法参数编码成 Http 请求的数据格式。

// feign包下
public interface Contract {
  //解析接口中的方法,保存成List
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);

   //实现类,
  abstract class BaseContract implements Contract {

      //提供了  parseAndValidatateMetadata 的实现 
       public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType){}
  }
   //此类中提供了一系列处理类上的注解,方法上的注解,参数上的注解的 方法. 
  class Default extends BaseContract{

       protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType){...}

       protected void processAnnotationOnMethod(MethodMetadata data,
                                             Annotation methodAnnotation,
                                             Method method) {... }

       protected boolean processAnnotationsOnParameter(MethodMetadata data,
                                                    Annotation[] annotations,
                                                    int paramIndex){  }
        

   }
}

总结: Contract 接口利用parseAndValidateMetadata()将 interface GitHub 中每个接口中的方法及其注解解析为 MethodMetadata,另外  SpringMvcContract 也是实现了 Contract 接口的一个子类,它处理了SpringMvc提供的注解. 

接下来,请查看上一篇文章的   <<  2.3 MethodHandler 方法执行器 >> 这一部分,在targetToHandlersByName.apply(target);中,调用了  List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type()); 

接着循环 metadata, 创建 BuildTemplateByResolvingArgs buildTemplate对象,

for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        result.put(md.configKey(),
            factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}

最后调用  factory.create(),创建一个MethodHandler对象. 

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

这个 SynchronousMethodHandler 实现了 MethodHandler, 它的invoke() 方法是一个回调方法,当调用代理对象的被代理方法时,jvm会回调此方法.  以下invoke()执行过程中,由buildTemplateFromArgs.create()创建了一个RequestTemplate对象。

@Override
  public Object invoke(Object[] argv) throws Throwable {
    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;
      }
    }
  }

然后在    executeAndDecode(template,options)中,根据template生成了Request对象,并加入options等参数后发出请求,并得到响应。 

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);    //****关键

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

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);    //*****关键
    } 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);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);  //****关键
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

以上是调用 Client#execute 发送 Http 请求。Client本身是一个接口,它里面有一个执行发请求的方法

Response execute(Request request, Options options) throws IOException;
public interface Client {
    ....
    Response execute(Request request, Options options) throws IOException;
}

总结: Client 的具体实现有 HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty 等。

以上就是整个执行的流程。

下面我们只关注前三步:即 Feign 方法元信息解析及参数编码过程。

2. Contract 方法注解及元信息解析

以 Feign 默认的 Contract.Default 为例( 除此外还有 JAXRSContract,  HystrixDelegatingContract等):

首先回顾一下 Feign 注解的使用(@RequestLine @Headers @Body @Param @HeaderMap @QueryMap):

//假如有以下的接口
@Headers("Content-Type: application/json")
interface UserService {
    @RequestLine("POST /user")
    @Headers("Content-Type: application/json")
    @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
    void user(@Param("user_name") String name, @Param("password") String password, 
              @QueryMap Map<String, Object> queryMap, 
              @HeaderMap Map<String, Object> headerMap, User user);
}

Contract解析注解的过程

总结: Contract.BaseContract#parseAndValidatateMetadata 会遍历解析 UserService 中的每个方法,按接口类上、方法上、参数上的注解,将其解析成 MethodMetadata。

protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
     MethodMetadata data = new MethodMetadata();
     data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
     data.configKey(Feign.configKey(targetType, method));

     // 1. 解析类上的注解
     if (targetType.getInterfaces().length == 1) {
         processAnnotationOnClass(data, targetType.getInterfaces()[0]);
     }
     processAnnotationOnClass(data, targetType);

     // 2. 解析方法上的注解
     for (Annotation methodAnnotation : method.getAnnotations()) {
         processAnnotationOnMethod(data, methodAnnotation, method);
     }
     Class<?>[] parameterTypes = method.getParameterTypes();
     Type[] genericParameterTypes = method.getGenericParameterTypes();

     Annotation[][] parameterAnnotations = method.getParameterAnnotations();
     int count = parameterAnnotations.length;
     for (int i = 0; i < count; i++) {
         // isHttpAnnotation 表示参数上是否有注解存在
         boolean isHttpAnnotation = false;
         if (parameterAnnotations[i] != null) {
             isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
         }
         // 方法参数上不存在注解
         if (parameterTypes[i] == URI.class) {
             data.urlIndex(i);
         } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
             // 已经设置过 @FormParam JAX-RS规范
             checkState(data.formParams().isEmpty(),
                        "Body parameters cannot be used with form parameters.");
             // 已经设置过 bodyIndex,如 user(User user1, Person person) ×
             checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
             data.bodyIndex(i);
             data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
         }
     }

     return data;
 }

这个方法也很好理解,接下来看一下 @RequestLine @Headers @Body @Param @HeaderMap @QueryMap 这些注解的具体解析过程。

2.1 processAnnotationOnClass

@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) {
    if (targetType.isAnnotationPresent(Headers.class)) {
        String[] headersOnType = targetType.getAnnotation(Headers.class).value();
        checkState(headersOnType.length > 0, "Headers annotation was empty on type %s.",
                targetType.getName());
        Map<String, Collection<String>> headers = toMap(headersOnType);
        headers.putAll(data.template().headers());
        data.template().headers(null); // to clear
        data.template().headers(headers);
    }
}

总结: 类上只有一个注解:

  1. @Headers -> data.template().headers

2.2 processAnnotationOnMethod

protected void processAnnotationOnMethod(
    MethodMetadata data, Annotation methodAnnotation, Method method) {
    Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
    if (annotationType == RequestLine.class) {
        String requestLine = RequestLine.class.cast(methodAnnotation).value();
        checkState(emptyToNull(requestLine) != null,
                   "RequestLine annotation was empty on method %s.", method.getName());

        Matcher requestLineMatcher = REQUEST_LINE_PATTERN.matcher(requestLine);
        if (!requestLineMatcher.find()) {
            throw new IllegalStateException(String.format(
                "RequestLine annotation didn't start with an HTTP verb on method %s",
                method.getName()));
        } else {
            data.template().method(HttpMethod.valueOf(requestLineMatcher.group(1)));
            data.template().uri(requestLineMatcher.group(2));
        }
        data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());
        data.template()
            .collectionFormat(RequestLine.class.cast(methodAnnotation).collectionFormat());

    } else if (annotationType == Body.class) {
        String body = Body.class.cast(methodAnnotation).value();
        checkState(emptyToNull(body) != null, "Body annotation was empty on method %s.",
                   method.getName());
        if (body.indexOf('{') == -1) {
            data.template().body(body);
        } else {
            data.template().bodyTemplate(body);
        }
    } else if (annotationType == Headers.class) {
        String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
        checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.",
                   method.getName());
        data.template().headers(toMap(headersOnMethod));
    }
}

总结: 方法上可能有三个注解:

  1. @RequestLine -> data.template().method + data.template().uri
  2. @Body -> data.template().body
  3. @Headers -> data.template().headers

2.3 processAnnotationsOnParameter

protected boolean processAnnotationsOnParameter(
    MethodMetadata data, Annotation[] annotations,int paramIndex) {
    boolean isHttpAnnotation = false;
    for (Annotation annotation : annotations) {
        Class<? extends Annotation> annotationType = annotation.annotationType();
        if (annotationType == Param.class) {
            Param paramAnnotation = (Param) annotation;
            String name = paramAnnotation.value();
            checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.",
                       paramIndex);
            nameParam(data, name, paramIndex);
            Class<? extends Param.Expander> expander = paramAnnotation.expander();
            if (expander != Param.ToStringExpander.class) {
                data.indexToExpanderClass().put(paramIndex, expander);
            }
            data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());
            isHttpAnnotation = true;
            // 即不是@Headers和@Body上的参数,只能是formParams了
            if (!data.template().hasRequestVariable(name)) {
                data.formParams().add(name);
            }
        } else if (annotationType == QueryMap.class) {
            checkState(data.queryMapIndex() == null,
                       "QueryMap annotation was present on multiple parameters.");
            data.queryMapIndex(paramIndex);
            data.queryMapEncoded(QueryMap.class.cast(annotation).encoded());
            isHttpAnnotation = true;
        } else if (annotationType == HeaderMap.class) {
            checkState(data.headerMapIndex() == null,
                       "HeaderMap annotation was present on multiple parameters.");
            data.headerMapIndex(paramIndex);
            isHttpAnnotation = true;
        }
    }
    return isHttpAnnotation;
}

总结: 参数上可能有三个注解:

  1. @Param-> data.indexToName

  2. @QueryMap-> data.queryMapIndex

  3. @HeaderMap-> data.headerMapIndex

2.4 MethodMetadata

  上面解析方法的元信息,目的就是为了屏蔽 Feign、JAX-RS 1/2、Spring Web MVC 等 REST 声明式注解的差异,那 MethodMetadata 到底有那些信息呢?

public final class MethodMetadata implements Serializable {

  private static final long serialVersionUID = 1L;
  private String configKey;   // 方法签名,类全限名+方法全限名
  private transient Type returnType;   // 方法返回值类型
  private Integer urlIndex;   // 方法参数为url时,为 urlIndex
  private Integer bodyIndex;  // 方法参数没有任务注解,默认为 bodyIndex
  private Integer headerMapIndex;
  private Integer queryMapIndex;
  private boolean queryMapEncoded;
  private transient Type bodyType;
  private RequestTemplate template = new RequestTemplate();      // 核心
  private List<String> formParams = new ArrayList<String>();
  private Map<Integer, Collection<String>> indexToName =
      new LinkedHashMap<Integer, Collection<String>>();
  private Map<Integer, Class<? extends Expander>> indexToExpanderClass =
      new LinkedHashMap<Integer, Class<? extends Expander>>();
  private Map<Integer, Boolean> indexToEncoded = new LinkedHashMap<Integer, Boolean>();
  private transient Map<Integer, Expander> indexToExpander;

}

总结: 到目前为至,Method 的方法的参数已经解析成 MethodMetadata,当方法调用时,会根据 MethodMetadata 的元信息将 argv 解析成 Request。

3. 参数解析成 Request

以 BuildTemplateByResolvingArgs 为例。

public RequestTemplate create(Object[] argv) {
    RequestTemplate mutable = RequestTemplate.from(metadata.template());
    // 1. 解析url参数
    if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null,
                      "URI parameter %s was null", urlIndex);
        mutable.target(String.valueOf(argv[urlIndex]));
    }
    // 2. 解析参数argv成对应的对象
    Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
    for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
            if (indexToExpander.containsKey(i)) {
                value = expandElements(indexToExpander.get(i), value);
            }
            for (String name : entry.getValue()) {
                varBuilder.put(name, value);
            }
        }
    }

    // 3. @Body中的参数占位符
    RequestTemplate template = resolve(argv, mutable, varBuilder);
    // 4. @QueryMap
    if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        Object value = argv[metadata.queryMapIndex()];
        Map<String, Object> queryMap = toQueryMap(value);
        template = addQueryMapQueryParameters(queryMap, template);
    }

    // 5. @HeaderMap
    if (metadata.headerMapIndex() != null) {
        template =
            addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
    }

    return template;
}

总结: 将方法的参数解析成 RequestTemplate 后就简单了,只需要调用 request 即可最终解析成 Request。可以看到 Request 包含了 Http 请求的全部信息。到此,Feign 的参数解析全部完成。

public Request request() {
    if (!this.resolved) {
        throw new IllegalStateException("template has not been resolved.");
    }
    return Request.create(this.method, this.url(), this.headers(), this.requestBody());
}

4. 以后只需要实现自己的 Contract,将对应的注解信息解析成 MethodMetadata,即可完成适配工作。

  1. jaxrs Feign 原生支持,感兴趣的可以看一下其实现:feign.jaxrs.JAXRSContract
  2. Spring Web MVC Spring Cloud OpenFeign 提供了支持
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyingchengqi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值