ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-09-09 11:03:27  阅读:238  来源: 互联网

标签:Feign -- uri 源码 参数 注解 解析 data method


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

接口

该接口的作用就是解析类中的方法。每个方法解析为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;
	}

看下参数处理器有哪些
在这里插入图片描述

解析请求参数

解析的是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;
    }
  }

标签:Feign,--,uri,源码,参数,注解,解析,data,method
来源: https://blog.csdn.net/qq_34501351/article/details/120138047

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有