시리즈를 척하기 (04) 계약의 소스 코드를 구문 분석

시리즈를 척하기 (04) 계약의 소스 코드를 구문 분석

[TOC]

봄 클라우드 시리즈 카탈로그 ( https://www.cnblogs.com/binarylei/p/11563952.html#feign )

에서 이전 글 우리는 어떤 약 분석 Feign작동하고, 그 Feign결국은 행 HTTP 요청을 해결하기 위해 척하기, REST 선언 주석 JAX-RS 1/2, 방법의 매개 변수를 적용하는 방법, 요청 헤더, 요청입니다 몸은 무엇입니까? 여기에서 우리는 언급해야 Contract이 인터페이스를.

1. 척하기 전체적인 흐름 부호화 파라미터

도 1 : 전체 유량 파라미터 부호화 꾀병
sequenceDiagram 참가자 클라이언트 계약 - >> MethodMetadata : 1. 解析 方法 元 信息 : parseAndValidatateMetadata (클래스 TARGETTYPE <?>) MethodMetadata - >> RequestTemplate.Factory 2. 封装 MethodMetadata : buildTemplate RequestTemplate.Factory - >> RequestTemplate 3. 解析 方法4. 요청 클라이언트 - >> 요청 : : - : 参数 >> 요청 (변수는 argv) RequestTemplate.Factory을 만들 5. 发送 HTTP를 请求 : 실행 (요청 요청, 옵션 옵션)

요약 : 처음 두 단계는 Feign연기 생성 단계, 및 매개 변수 분석 방법은 메타 - 정보 주석. 위상, HTTP 요청의 데이터 포맷으로 부호화 방법 파라미터를 호출 세 단계 후.

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

요약 : 그 해결의 각 인터페이스의 계약 UserService 인터페이스 방법은 MethodMetadata을 언급, 다음 요청으로 인코딩 RequestTemplate 번호 요청을 사용합니다.

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

요약 : 당신은 클라이언트 # 실행 호출 할 수 있습니다 후 요청으로 인코딩 requestTemplate # 요청은 HTTP 요청을 보냅니다.

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

요약 : 콘크리트 실현 클라이언트 등 HttpURLConnection의 아파치 HttpComponnets, OkHttp3, 인 Netty하고있다. 척하기 방법 메타 정보 분석 파라미터 인코딩 과정이 문서에서는 처음 세 단계에 초점을 맞춘다.

2. 계약 주석 메타 정보 분석 방법

하려면 Feign기본적으로 Contract.Default예를 들어 :

첫째, 볼 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);
}
도 2 : 계약 메타 정보 분석 방법
sequenceDiagram 계약 - >> 방법 : 1. processAnnotationOnClass 계약 - >> 방법 : 2. processAnnotationOnMethod 계약 - >> 방법 : 방법 3. processAnnotationsOnParameter 참고 오른쪽 : 구문 분석 정당성의 <BR/>을 확인합니다

요약 : Contract.BaseContract#parseAndValidatateMetadata MethodMetadata로 파싱하는 인터페이스 클래스, 메소드, 파라미터에있어서 특수 UserService 해결 방법의 각각을 통과한다.

 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 () 헤더

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 () 메소드 + data.template () URI.
  2. @Body -.> data.template () 신체
  3. @Headers -.> data.template () 헤더

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

    표 1 : 주석 파싱에게 해당 값 꾀병
    척하기 코멘트 MethodMetadata 분석 값
    @Headers data.template (). 헤더
    @RequestLine data.template (). 방법 + data.template (). URI
    @몸 data.template (). 본체
    파라미터 : data.indexToName
    @QueryMap data.queryMapIndex
    @HeaderMap data.headerMapIndex

2.4 MethodMetadata

오랜 시간이 위의 설명을 잘, 그것은 목적은 보호하는 것입니다, 정보 분석 요소 방법입니다 Feign、JAX-RS 1/2、Spring Web MVC사람들은 그 MethodMetadata 정보가 거기에 다른 차이는 결국, 선언적 주석을 REST?

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;

요약 : 지금까지 매개 변수 방법 방법에 대한 메서드를 호출 할 MethodMetadata로 해석이는 위안 해상도를 argv를합니다 정보 MethodMetadata에 따라 요청 될 것입니다.

로 분석 3. 요청 매개 변수

예를 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에 해결, 그냥 요청에 대한 최종 해결에 요청을 호출합니다. 당신은 요청이 모든 정보를 HTTP 요청을 포함 볼 수 있습니다. 이, 척하기를 분석 인수가 완료되었습니다.

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. 사고 : 척하기 방법 호환 JAX-RS 1/2, 스프링 웹 MVC

확실히 우리는 적응 작업을 완료하는 그들의 계약의 MethodMetadata로 파싱에 해당하는 주석 정보를 실현하기 위해, 추측했다.

  1. jaxrs 기본적으로 지원 척하기, 구현 볼 수 관심이 :feign.jaxrs.JAXRSContract
  2. Spring Web MVC 봄 클라우드 OpenFeign 지원을 제공합니다

매일 조금 기록의 의도. 아마도 내용이 중요하지 않지만 습관이 매우 중요합니다!

추천

출처www.cnblogs.com/binarylei/p/11576148.html