Feign series (04) Contract parse the source code

Feign series (04) Contract parse the source code

[TOC]

Spring Cloud Series catalog ( https://www.cnblogs.com/binarylei/p/11563952.html#feign )

In the previous article we analyzed the roughly what Feignworks, and that Feignin the end is how to adapt Feign, JAX-RS 1/2 of REST declarative annotations, the parameters of the method to resolve the row Http request, the request headers, request body do? Here we have to mention Contractthis interface.

1. Feign overall flow encoding parameters

FIG 1: Feign overall flow parameter coding
sequenceDiagram participant Client Contract ->> MethodMetadata: 1. 解析方法元信息:parseAndValidatateMetadata(Class<?> targetType) MethodMetadata ->> RequestTemplate.Factory: 2. 封装 MethodMetadata:buildTemplate RequestTemplate.Factory ->> RequestTemplate: 3. 解析方法参数:create(argv) RequestTemplate.Factory ->> Request: 4. request Client ->> Request: 5. 发送Http请求:execute(Request request, Options options)

Summary: The first two steps are FeignActing generation phase, parameters and analytical methods annotated meta-information. After three steps calling phase, the coding method parameter into a data format of the Http request.

public interface Contract {
      List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

Summary: Contract UserService interface method in each interface in its resolve to comment MethodMetadata, then use RequestTemplate # request encoded as a Request.

public final class RequestTemplate implements Serializable {
  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());
  }
}

Summary: requestTemplate # Request encoded as a Request after you can call the Client # execute sends Http request.

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

Summary: concrete realization Client has HttpURLConnection, Apache HttpComponnets, OkHttp3, Netty and so on. This article focuses on the first three steps: Feign method that is meta information analysis and parameter encoding process.

2. Contract annotations and meta information analysis method

To Feigndefault Contract.Defaultas an example:

First, look at Feignthe use of annotations ( @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);
}
FIG 2: Contract meta information analyzing method
sequenceDiagram Contract - >> Method: 1. processAnnotationOnClass Contract - >> Method: 2. processAnnotationOnMethod Contract - >> Method: 3. processAnnotationsOnParameter Note right of Method: parsing will check the legitimacy <br/>

Summary: Contract.BaseContract#parseAndValidatateMetadata will traverse each of resolved UserService method according annotations on the interface class, the method, the parameter, which is parsed into 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;
 }

This method is also well understood, let's look at @RequestLine @Headers @Body @Param @HeaderMap @QueryMapthe specific resolution process these annotations.

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

Summary: the class has only one comment:

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

Summary: There may be annotated on three methods:

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

Summary: There may be annotated on three parameters:

  1. @Param-> data.indexToName

  2. @QueryMap-> data.queryMapIndex

  3. @HeaderMap-> data.headerMapIndex

    Table 1: Feign annotated parse the corresponding value
    Feign comment MethodMetadata the analytical value
    @Headers data.template().headers
    @RequestLine data.template().method + data.template().uri
    @Body data.template().body
    @Param data.indexToName
    @QueryMap data.queryMapIndex
    @HeaderMap data.headerMapIndex

2.4 MethodMetadata

Well, for a long time to explain above, it is for information analytical element method, the purpose is to shield Feign、JAX-RS 1/2、Spring Web MVCand other differences REST declarative annotations, in the end there are those that MethodMetadata information it?

private String configKey;			// 方法签名,类全限名+方法全限名
private transient Type returnType;	// 方法返回值类型
private Integer urlIndex;			// 方法参数为url时,为 urlIndex
private Integer bodyIndex;			// 方法参数没有任务注解,默认为 bodyIndex
private Integer headerMapIndex;		// @HeaderMap
private Integer queryMapIndex;		// @QueryMap
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;

Summary: So far to the parameter method Method has been parsed into MethodMetadata, when the method is called, the yuan will become Request according to information MethodMetadata will argv resolution.

3. Request parameter parsing into

To BuildTemplateByResolvingArgs example.

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

Summary: The method parameters resolve to RequestTemplate after simple, just call request to final resolution to Request. You can see the Request contains all the information Http request. This, the argument parsing Feign completed.

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. Thinking: Feign how compatible JAX-RS 1/2, Spring Web MVC

Surely we have guessed, only to realize their Contract, the annotation information corresponding to the parsed into MethodMetadata, to complete the adaptation work.

  1. jaxrs Feign native support, interested can look at its implementation:feign.jaxrs.JAXRSContract
  2. Spring Web MVC Spring Cloud OpenFeign provides support

The intentions of recording a little bit every day. Perhaps the content is not important, but the habit is very important!

Guess you like

Origin www.cnblogs.com/binarylei/p/11576148.html
Recommended