【Feign源码】解析方法的类--Contract

本篇文章介绍的是如何将方法中数据映射为请求数据,例如哪些是请求参数,哪些是请求体,哪些是请求头。。。

接口

该接口的作用就是解析类中的方法。每个方法解析为MethodMetadata。

public interface Contract {

  /**
   * Contract 提供接口,feign的原生实现是BaseContract,整合spring使用的是SpringMvcContract
   */
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
  
  }

该接口只有一个方法,传入的参数是开发者编写的接口的元信息;作用是将每个方法解析为一个MethodMetadata对象。

元信息MethodMetadata

  // 序列化
  private static final long serialVersionUID = 1L;
  // 每个方法的唯一标识
  private String configKey;
  // 方法的返回值
  private transient Type returnType;
  // 如果这个方法参数有URI类型的,记住这个索引
  private Integer urlIndex;
  // 记录方法体的索引值
  private Integer bodyIndex;
  // head中的数据使用map封装,记录索引值
  private Integer headerMapIndex;
  // 查询数据使用map封装,记录索引值
  private Integer queryMapIndex;
  // 是否编码查询map
  private boolean queryMapEncoded;
  private transient Type bodyType;
  // 请求数据模板。包含请求方法,请求参数,请求体和url
  private RequestTemplate template = new RequestTemplate();
  
  private List<String> formParams = new ArrayList<String>();
  // 每个方法参数的名称,key:参数的索引位置;value:注解中的value值
  private Map<Integer, Collection<String>> indexToName =
      new LinkedHashMap<Integer, Collection<String>>();
  // Expander类型,传入一个Object对象,返回一个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;

重要的是这些属性,剩下的就是get,set方法了,不同于JavaBean,这个类的方法和属性名一样,有参数就是set方法,没有参数就是get方法。其实是什么都行,没必要一个要按照JavaBean的格式。
这里介绍的解析过程是基于整合Spring的流程,如果你想让让feign解析自己的注解,只需要实现Contract接口,之后实现自己的逻辑即可。

实现类BaseContract

BaseContract#parseAndValidatateMetadata

    @Override
    // 类型class信息传入,该为开发者编写的接口class信息。
    public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
    	// 不能有泛型
      checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
          targetType.getSimpleName());
       // 继承的接口最多只能有一个
      checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
          targetType.getSimpleName());
      if (targetType.getInterfaces().length == 1) {
        checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,
            "Only single-level inheritance supported: %s",
            targetType.getSimpleName());
      }
      Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
      // 遍历方法,
      for (Method method : targetType.getMethods()) {
      	// 默认方法和静态方法都跳过
        if (method.getDeclaringClass() == Object.class ||
            (method.getModifiers() & Modifier.STATIC) != 0 ||
            Util.isDefault(method)) {
          continue;
        }
        // 解析出方法信息,放入缓存。
        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());
    }

这里相当于Feign的规范了:

  1. 接口不能有泛型
  2. 继承的接口最多一个(继承的那个接口不能再继承接口了,也就是说最多有一个父接口,父接口不能再有父接口了)

遍历接口的所有Public方法,解析出MethodMetadata,放入集合返回。

解析方法

BaseContract#parseAndValidateMetadata
解析接口类中的方法。入参:targetType:接口类;method:接口类中的方法。

protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
	  // 既然每个方法对应一个MethodMetadata,二话不说,直接创建对象。
      MethodMetadata data = new MethodMetadata();
      // 解析出返回值类型
      data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
      // 创建唯一标识
      data.configKey(Feign.configKey(targetType, method));
	  // 有接口就先解析父接口的,之后再解析本接口的。
      if (targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
      }
      // 解析类上的注解
      processAnnotationOnClass(data, targetType);

	// 解析方法上的注解
      for (Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
      }

	// 到此为止,方法上的注解会解析出请求方法的。此处校验下
      checkState(data.template().method() != null,
          "Method %s not annotated with HTTP method type (ex. GET, POST)",
          method.getName());
      // 每个参数的元信息
      Class<?>[] parameterTypes = method.getParameterTypes();
      // 每个参数的泛型
      Type[] genericParameterTypes = method.getGenericParameterTypes();
	  // 每个方法参数的注解
      Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      int count = parameterAnnotations.length;
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        // 如果有注解的话,解析注解
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }
        // 如果是URI类型的,记录下索引,说明url不是从RequestMapping中解析出url,而是方法中传进来的。
        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
        // 不是http注解?记录请求体的索引,解析出参数类型
          checkState(data.formParams().isEmpty(),
              "Body parameters cannot be used with form parameters.");
          checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
          data.bodyIndex(i);
          data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
        }
      }

	
	// 如果解析完这个参数之后,headerMapIndex有值了,就是做个校验。。key一定是String类型。
      if (data.headerMapIndex() != null) {
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],
            genericParameterTypes[data.headerMapIndex()]);
      }

      if (data.queryMapIndex() != null) {
        if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
          checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
        }
      }

      return data;
    }

流程梳理:

  1. 先解析类上的注解信息,如果继承接口,先解析父接口的
  2. 再解析方法中的注解
  3. 最后解析方法参数的注解信息。
    这里的解析是什么意思呢?就是要知道哪些是url,哪些是请求参数,哪些是请求体,哪些是请求头,还没有调用方法,怎么知道真正的值呢?使用占位符代替。。。怎么操作的呢?
    下面就开始分析。。

解析标注在类上的注解

SpringMvcContract#processAnnotationOnClass

	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		//首先这个类一定是每个继承其他的类
		if (clz.getInterfaces().length == 0) {
			// 只找到RequestMapping注解
			RequestMapping classAnnotation = findMergedAnnotation(clz,
					RequestMapping.class);
			if (classAnnotation != null) {
				// Prepend path from class annotation if specified
				if (classAnnotation.value().length > 0) {
					// 得到path信息
					String pathValue = emptyToNull(classAnnotation.value()[0]);
					// 解析路径中的占位符 ${}
					pathValue = resolve(pathValue);
					if (!pathValue.startsWith("/")) {
						pathValue = "/" + pathValue;
					}
					// 赋值url
					data.template().uri(pathValue);
				}
			}
		}
	}

这里主要是解析出url的值,赋值给RequestTemplate,看下RequestTemplate.uri()做了什么?

  public RequestTemplate uri(String uri, boolean append) {
    /* validate and ensure that the url is always a relative one */
    if (UriUtils.isAbsolute(uri)) {
      throw new IllegalArgumentException("url values must be not be absolute.");
    }

    if (uri == null) {
      uri = "/";
    } else if ((!uri.isEmpty() && !uri.startsWith("/") && !uri.startsWith("{")
        && !uri.startsWith("?") && !uri.startsWith(";"))) {
      /* if the start of the url is a literal, it must begin with a slash. */
      uri = "/" + uri;
    }

    // 如果uri中有请求参数,解析出QueryTemplate
    // uri截取自己url的部分
    Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);
    if (queryMatcher.find()) {
      String queryString = uri.substring(queryMatcher.start() + 1);

      /* parse the query string */
      this.extractQueryTemplates(queryString, append);

      /* reduce the uri to the path */
      uri = uri.substring(0, queryMatcher.start());
    }

    int fragmentIndex = uri.indexOf('#');
    if (fragmentIndex > -1) {
      fragment = uri.substring(fragmentIndex);
      uri = uri.substring(0, fragmentIndex);
    }

    // 条件判断是追加还是创建
    if (append && this.uriTemplate != null) {
      this.uriTemplate = UriTemplate.append(this.uriTemplate, uri);
    } else {
      this.uriTemplate = UriTemplate.create(uri, !this.decodeSlash, this.charset);
    }
    return this;
  }

解析请求参数的部分:

  private void extractQueryTemplates(String queryString, boolean append) {
    // 这里有一点需要注意,把name一样的放在一个集合中。
    Map<String, List<String>> queryParameters =
        Arrays.stream(queryString.split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(
                SimpleImmutableEntry::getKey,
                LinkedHashMap::new,
                Collectors.mapping(Entry::getValue, Collectors.toList())));

    /* add them to this template */
    if (!append) {
      /* clear the queries and use the new ones */
      this.queries.clear();
    }
    // 每个entry创建QueryTemplate.
    queryParameters.forEach(this::query);
  }

举个例子:
uri: /user/info?name={name1}&age={age}&name={name2}
最后解析出:
url:/user/info
query:map中的值:key:name value:{name1},{name2}; key:age,value:{age}

解析标注在方法上的注解

SpringMvcContract#processAnnotationOnMethod

	protected void processAnnotationOnMethod(MethodMetadata data,
			Annotation methodAnnotation, Method method) {
		// 如果不存在RequestMapping注解或者传入的注解不是RequestMapping注解,直接返回了。
		// 解析方法额时候,只解析RequestMapping注解。
		if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
				.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}

		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// HTTP Method
		RequestMethod[] methods = methodMapping.method();
		if (methods.length == 0) {
			methods = new RequestMethod[] { RequestMethod.GET };
		}
		checkOne(method, methods, "method");
		// 解析出请求方法,如果没有设置,默认使用的是GET方法
		data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

		// path
		checkAtMostOne(method, methodMapping.value(), "value");
		if (methodMapping.value().length > 0) {
			String pathValue = emptyToNull(methodMapping.value()[0]);
			if (pathValue != null) {
				pathValue = resolve(pathValue);
				// Append path from @RequestMapping if value is present on method
				if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
					pathValue = "/" + pathValue;
				}
				// 解析出url,这次是拼接在类url之后。
				data.template().uri(pathValue, true);
			}
		}

		// 下面的三个可以归为设置请求头
		parseProduces(data, method, methodMapping);

		// consumes
		parseConsumes(data, method, methodMapping);

		// headers
		parseHeaders(data, method, methodMapping);

		data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
	}

解析方法上的注解,只能解析RequestMapping注解,得到请求方法,url,另外parseProduces,parseConsumes,parseHeaders是得到请求头的值,创建QueryTemplate.需要注意的是parseProduces,parseConsumes指定了类型,如果在RequestMapping中指定了多个,只会去第一个。个人觉得,不要在RequestMapping中指定对象头信息,在参数中指定。

解析标注在方法参数上的注解

SpringMvcContract#processAnnotationsOnParameter
传参:方法元信息;方法参数的注解数组,因为可能有多个;方法参数的索引

	protected boolean processAnnotationsOnParameter(MethodMetadata data,
			Annotation[] annotations, int paramIndex) {
		boolean isHttpAnnotation = false;

		AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(
				data, paramIndex);
		Method method = this.processedMethods.get(data.configKey());
		// 遍历注解
		for (Annotation parameterAnnotation : annotations) {
			// 得到对应的参数处理器
			AnnotatedParameterProcessor processor = this.annotatedArgumentProcessors
					.get(parameterAnnotation.annotationType());
			if (processor != null) {
				Annotation processParameterAnnotation;
				// synthesize, handling @AliasFor, while falling back to parameter name on
				// missing String #value():
				processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
						parameterAnnotation, method, paramIndex);
				// 处理器解析。
				isHttpAnnotation |= processor.processArgument(context,
						processParameterAnnotation, method);
			}
		}
		
		// 这里只是获得该位置参数的类型转换器
		if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {
			TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
			if (this.conversionService.canConvert(typeDescriptor,
					STRING_TYPE_DESCRIPTOR)) {
				Param.Expander expander = this.convertingExpanderFactory
						.getExpander(typeDescriptor);
				if (expander != null) {
					data.indexToExpander().put(paramIndex, expander);
				}
			}
		}
		return isHttpAnnotation;
	}

看下参数处理器有哪些
【Feign源码】解析方法的类--Contract

解析请求参数

解析的是RequestParam注解
RequestParamParameterProcessor#processArgument

	public boolean processArgument(AnnotatedParameterContext context,
			Annotation annotation, Method method) {
		// 得到该参数索引
		int parameterIndex = context.getParameterIndex();
		// 得到该参数的类型
		Class<?> parameterType = method.getParameterTypes()[parameterIndex];
		
		MethodMetadata data = context.getMethodMetadata();
		
		// 如果map类型的,就不再解析了,设置queryMapIndex的索引位置
		if (Map.class.isAssignableFrom(parameterType)) {
			checkState(data.queryMapIndex() == null,
					"Query map can only be present once.");
			data.queryMapIndex(parameterIndex);

			return true;
		}
		
		// 如果不是map类型,进行解析
		RequestParam requestParam = ANNOTATION.cast(annotation);
		String name = requestParam.value();
		checkState(emptyToNull(name) != null,
				"RequestParam.value() was empty on parameter %s", parameterIndex);
		// 设置该位置的索引--名称 对应关系
		context.setParameterName(name);

		// 将该占位符的信息添加在list老的集合中之后重新设置给template
		Collection<String> query = context.setTemplateParameter(name,
				data.template().queries().get(name));
		data.template().query(name, query);
		return true;
	}

这里的设置占位符使用的是注解的RequestParam.value值加上{},即{equestParam.value};将该值添加在query集合中。

解析请求头

解析的是RequestHeader注解
RequestHeaderParameterProcessor#processArgument
和处理请求参数的逻辑一致,不再分析了。

	@Override
	public boolean processArgument(AnnotatedParameterContext context,
			Annotation annotation, Method method) {
		int parameterIndex = context.getParameterIndex();
		Class<?> parameterType = method.getParameterTypes()[parameterIndex];
		MethodMetadata data = context.getMethodMetadata();

		if (Map.class.isAssignableFrom(parameterType)) {
			checkState(data.headerMapIndex() == null,
					"Header map can only be present once.");
			data.headerMapIndex(parameterIndex);

			return true;
		}

		String name = ANNOTATION.cast(annotation).value();
		checkState(emptyToNull(name) != null,
				"RequestHeader.value() was empty on parameter %s", parameterIndex);
		context.setParameterName(name);

		Collection<String> header = context.setTemplateParameter(name,
				data.template().headers().get(name));
		data.template().header(name, header);
		return true;
	}

解析表单参数

解析的注解是:PathVariable
PathVariableParameterProcessor#processArgument

	@Override
	public boolean processArgument(AnnotatedParameterContext context,
			Annotation annotation, Method method) {
		String name = ANNOTATION.cast(annotation).value();
		checkState(emptyToNull(name) != null,
				"PathVariable annotation was empty on param %s.",
				context.getParameterIndex());
		context.setParameterName(name);
		
		MethodMetadata data = context.getMethodMetadata();
		// 得到注解的value;
		String varName = '{' + name + '}';
		// 这里的值:url没有,请求参数中没有,请求头中没有,则会加入在表达参数中。
		if (!data.template().url().contains(varName)
				&& !searchMapValues(data.template().queries(), varName)
				&& !searchMapValues(data.template().headers(), varName)) {
			data.formParams().add(name);
		}
		return true;
	}

每个参数的注解有不同的处理器,目前只有@requestParam 处理请求参数;@requestHead处理请求头;@pathVariable处理path中的参数。 @SpringQueryMap处理请求参数,前三个都会记录别名和索引,之后分别解析。

解析请求参数集合

解析的注解是SpringQueryMap
QueryMapParameterProcessor#processArgument

	@Override
	public boolean processArgument(AnnotatedParameterContext context,
			Annotation annotation, Method method) {
		int paramIndex = context.getParameterIndex();
		MethodMetadata metadata = context.getMethodMetadata();
		// 如果使用该注解标注,map类型 和使用RequestParam map类型等效。
		if (metadata.queryMapIndex() == null) {
			metadata.queryMapIndex(paramIndex);
			metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
		}
		return true;
	}

小结:
RequestParam注解设置的是拼接在url上的参数
ReqestHead注解设置的是请求头参数
PathVariable注解设置的是post方法,请求体中的参数。

发现了吧,怎么没有解析请求体的逻辑呢?其实没有,只是记录的body的索引。它只是解析以上的四个注解。不是的的话一律不解析的。返回的是isHttpAnnotation,如果是false,类型不是Request.Options.class,那么会进行判断,设置请求体的索引。

if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
          checkState(data.formParams().isEmpty(),
              "Body parameters cannot be used with form parameters.");
          checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
          data.bodyIndex(i);
          data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
        }

调用

ReflectiveFeign.ParseHandlersByName#apply

    public Map<String, MethodHandler> apply(Target key) {
    // 解析出所有的方法元信息
      List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      // 遍历元信息,根据元信息中属性的不同,使用不同的RequestTemplate创建器
      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) {
        	// 如果请求体的索引不为null
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
          // 最后:什么也没有,只解析url,请求参数,请求头
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        result.put(md.configKey(),
            factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
      }
      return result;
    }
  }
上一篇:LINQ中的一些查询语句格式


下一篇:Linq使用Group By 1