Feign原理解析(三)参数的处理

参数的处理

之前的章节提到过接口方法的解析,现在再看一下这个方法:

    public Map<String, MethodHandler> apply(Target key) {
      List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      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));
      }
      return result;
    }

经过Contract类的parseAndValidatateMetadata(Class targetType)方法解析过接口中的方法之后,会得出方法元数据MethodMetadata的集合,然后需要遍历这个集合,根据不同的条件创建RequestTemplate请求模板构建类BuildTemplateByResolvingArgs的实例,这里有三种:

  • 1.表单处理实例:BuildFormEncodedTemplateFromArgs
  • 2.包含请求体body处理实例:BuildEncodedTemplateFromArgs
  • 3.除上述两种之外的:BuildTemplateByResolvingArgs
RequestTemplate.Factory
  interface Factory {

    /**
     * create a request template using args passed to a method invocation.
     */
    RequestTemplate create(Object[] argv);
  }

这个接口只有一个方法,就是创建RequestTemplate 请求模板

BuildTemplateByResolvingArgs关键方法解析
重写RequestTemplate.Factory的RequestTemplate create(Object[] argv)
    @Override
    public RequestTemplate create(Object[] argv) {
      RequestTemplate mutable = new RequestTemplate(metadata.template());
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.insert(0, String.valueOf(argv[urlIndex]));
      }
      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);
          }
        }
      }

      RequestTemplate template = resolve(argv, mutable, varBuilder);
      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);
      }

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

      return template;
    }

分析如下:

  • 1.复制元数据MethodMetadata的RequestTemplate
  • 2.处理URI类型参数
  • 3.获取@Param参数
  • 4.替换模板,处理参数
  • 5.处理@QueryMap参数
  • 6.处理@HeaderMap参数
将@QueryMap注解声明的对象转化为Map:toQueryMap (Object value)
    private Map<String, Object> toQueryMap (Object value) {
     if (value instanceof Map) {
        return (Map<String, Object>)value;
      }
      try {
        return queryMapEncoder.encode(value);
      } catch (EncodeException e) {
        throw new IllegalStateException(e);
      }
    }

如果被@QueryMap注解声明的对象是Map类型则直接返回,否则将被QueryMapEncoder对象编码成Map对象,Feign默认的QueryMapEncoder如下:

 class Default implements QueryMapEncoder {

    private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
        new HashMap<Class<?>, ObjectParamMetadata>();

    @Override
    public Map<String, Object> encode (Object object) throws EncodeException {
      try {
        ObjectParamMetadata metadata = getMetadata(object.getClass());
        Map<String, Object> fieldNameToValue = new HashMap<String, Object>();
        for (Field field : metadata.objectFields) {
          Object value = field.get(object);
          if (value != null && value != object) {
            fieldNameToValue.put(field.getName(), value);
          }
        }
        return fieldNameToValue;
      } catch (IllegalAccessException e) {
        throw new EncodeException("Failure encoding object into query map", e);
      }
    }

    private ObjectParamMetadata getMetadata(Class<?> objectType) {
      ObjectParamMetadata metadata = classToMetadata.get(objectType);
      if (metadata == null) {
        metadata = ObjectParamMetadata.parseObjectType(objectType);
        classToMetadata.put(objectType, metadata);
      }
      return metadata;
    }

    private static class ObjectParamMetadata {

      private final List<Field> objectFields;

      private ObjectParamMetadata (List<Field> objectFields) {
        this.objectFields = Collections.unmodifiableList(objectFields);
      }

      private static ObjectParamMetadata parseObjectType(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();
        for (Field field : type.getDeclaredFields()) {
          if (!field.isAccessible()) {
            field.setAccessible(true);
          }
          fields.add(field);
        }
        return new ObjectParamMetadata(fields);
      }
    }
  }

这个类主要就是利用反射解析class中的field,并做了一个小小的缓存,之后再填充参数到Map中

将解析@QueryMap获取的Map放入queries中:addQueryMapQueryParameters
    private RequestTemplate addQueryMapQueryParameters(Map<String, Object> queryMap, RequestTemplate mutable) {
      for (Entry<String, Object> currEntry : queryMap.entrySet()) {
        Collection<String> values = new ArrayList<String>();

        boolean encoded = metadata.queryMapEncoded();
        Object currValue = currEntry.getValue();
        if (currValue instanceof Iterable<?>) {
          Iterator<?> iter = ((Iterable<?>) currValue).iterator();
          while (iter.hasNext()) {
            Object nextObject = iter.next();
            values.add(nextObject == null ? null : encoded ? nextObject.toString() : RequestTemplate.urlEncode(nextObject.toString()));
          }
        } else {
          values.add(currValue == null ? null : encoded ? currValue.toString() : RequestTemplate.urlEncode(currValue.toString()));
        }

        mutable.query(true, encoded ? currEntry.getKey() : RequestTemplate.urlEncode(currEntry.getKey()), values);
      }
      return mutable;
    }

遍历Map,根据@QueryMap的encoded参数来判断是否需要对value进行url encode,然后再通过RequestTemplate 的query方法将key-value放入queries中

将值转换为字符串:expandElements
    private Object expandElements(Expander expander, Object value) {
      if (value instanceof Iterable) {
        return expandIterable(expander, (Iterable) value);
      }
      return expander.expand(value);
    }

    private List<String> expandIterable(Expander expander, Iterable value) {
      List<String> values = new ArrayList<String>();
      for (Object element : value) {
        if (element!=null) {
          values.add(expander.expand(element));
        }
      }
      return values;
    }

如果value属于Iterable类型(例如List),则调用expandIterable方法展开L并转换为String列表,转换的过程就是调用Expander 的expand方法转换为字符串,默认的是ToStringExpander,也就是调用value的toString方法直接转换

将@HeaderMap注解声明的Map放入headers中:addQueryMapQueryParameters
    private RequestTemplate addHeaderMapHeaders(Map<String, Object> headerMap, RequestTemplate mutable) {
      for (Entry<String, Object> currEntry : headerMap.entrySet()) {
        Collection<String> values = new ArrayList<String>();

        Object currValue = currEntry.getValue();
        if (currValue instanceof Iterable<?>) {
          Iterator<?> iter = ((Iterable<?>) currValue).iterator();
          while (iter.hasNext()) {
            Object nextObject = iter.next();
            values.add(nextObject == null ? null : nextObject.toString());
          }
        } else {
          values.add(currValue == null ? null : currValue.toString());
        }

        mutable.header(currEntry.getKey(), values);
      }
      return mutable;
    }

这个处理过程跟@QueryMap差不多,就不赘述了,有一点需要注意的

  • @QueryMap是从Feign 9.7版本才开始支持pojo的,9.7版本以前是跟@HeaderMap一样只能声明Map类型的参数
参数处理:resolve(Object[] argv, RequestTemplate mutable,Map variables)
    protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable,
                                      Map<String, Object> variables) {
      // Resolving which variable names are already encoded using their indices
      Map<String, Boolean> variableToEncoded = new LinkedHashMap<String, Boolean>();
      for (Entry<Integer, Boolean> entry : metadata.indexToEncoded().entrySet()) {
        Collection<String> names = metadata.indexToName().get(entry.getKey());
        for (String name : names) {
          variableToEncoded.put(name, entry.getValue());
        }
      }
      return mutable.resolve(variables, variableToEncoded);
    }

BuildTemplateByResolvingArgs的resolve方法只是简单的将需要encode的参数加入variableToEncoded集合,然后交由RequestTemplate进行处理

form表单参数解析:BuildFormEncodedTemplateFromArgs
  private static class BuildFormEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {

    private final Encoder encoder;

    private BuildFormEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder, QueryMapEncoder queryMapEncoder) {
      super(metadata, queryMapEncoder);
      this.encoder = encoder;
    }

    @Override
    protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable,
                                      Map<String, Object> variables) {
      Map<String, Object> formVariables = new LinkedHashMap<String, Object>();
      for (Entry<String, Object> entry : variables.entrySet()) {
        if (metadata.formParams().contains(entry.getKey())) {
          formVariables.put(entry.getKey(), entry.getValue());
        }
      }
      try {
        encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);
      } catch (EncodeException e) {
        throw e;
      } catch (RuntimeException e) {
        throw new EncodeException(e.getMessage(), e);
      }
      return super.resolve(argv, mutable, variables);
    }
  }

这个类是继承自BuildTemplateByResolvingArgs,重写了父类的resolve方法,在@Param声明的参数列表中找出属于form的数据,然后再encode放入RequestTemplate的body当中,然后再调用父类的resolve方法处理其他参数

默认的Encoder:Default
  class Default implements Encoder {

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
        template.body(object.toString());
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if (object != null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass()));
      }
    }
  }

这里需要注意一下,Feign默认的Encoder只处理String 和 byte[] 类型的如果是其他类型并且参数不为null时将抛出EncodeException异常,当然,Feign也集成了一些第三方的Json处理包,比如Jackson和Gson等都可以处理这类数据

请求体body处理:BuildEncodedTemplateFromArgs
  private static class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {

    private final Encoder encoder;

    private BuildEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder, QueryMapEncoder queryMapEncoder) {
      super(metadata, queryMapEncoder);
      this.encoder = encoder;
    }

    @Override
    protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable,
                                      Map<String, Object> variables) {
      Object body = argv[metadata.bodyIndex()];
      checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());
      try {
        encoder.encode(body, metadata.bodyType(), mutable);
      } catch (EncodeException e) {
        throw e;
      } catch (RuntimeException e) {
        throw new EncodeException(e.getMessage(), e);
      }
      return super.resolve(argv, mutable, variables);
    }
  }

这个类是继承自BuildTemplateByResolvingArgs,重写了父类的resolve方法,在参数列表中找出属于body的数据(之前提到过,没有被@Param、@QueryMap、@HeaderMap注解声明过,并且不是URI类型的参数的就属于body类型参数,而且body类型参数不能与form类型参数共存并且只能有一个body类型参数,否则会报错),然后再encode放入RequestTemplate的body当中,然后再调用父类的resolve方法处理其他参数

你可能感兴趣的:(Feign原理解析)