Open Feign 源码解析(四) --- 请求对象构造(上)

news2025/1/10 17:52:59

Open Feign 源码解析四 请求对象的构造(上)

源码前三篇文章写了这个图的过程

在这里插入图片描述

源码前三篇文章的内容归纳起来就是讲了这样的问题:

如何把接口转换为具有发送http请求能力的feign client对象以及如何整合到Spring容器中?

如何构造请求对象?

思路分析

Http请求对象的分析(目标)

URL: http://127.0.0.1:9000/consumer/feign/order/{1}?name=xxx&age=18

​ 协议: http

​ IP端口: 127.0.0.1:9000 -> 注册中心获取

​ URI: /consumer/feign/order/{id}

​ 路径参: {1} (path variable)

​ 请求参:name=xxx, age=18 (query)

请求头: headers

请求体: body

请求方法: Get/Post/Put/Delete …

public final class Request {
  private final HttpMethod httpMethod;
  private final String url;
  private final Map<String, Collection<String>> headers;
  private final Body body;
}
接口方法的分析(数据源)

方法本身的要素是否能表达所有Http请求的要素?

方法的要素:

​ 方法名 ×

​ 参数(名称与类型) √

​ 返回值类型 ×

URI -> 注解 或 Java对象(URI对象)表示

请求方法 -> 注解

路径参、请求参、请求头、请求体 -> 方法的入参 + 注解

问题一:注解如何设计?

1)URI 和 请求方法可以合并在一个注解中

2)对路径参、请求参、请求头、请求体分别设置对应的注解

feign:

@RequestLine/@Param/@QueryMap/@HeaderMap/@Body

open feign:

@RequestMapping/@PathVariable/@RequestParam/@SpringQueryMap/@RequestHeader/@RequestBody

URI: 类的@RequestMapping + 方法的@RequestMapping

请求方法: 方法的@RequestMapping

路径参:参数的@PathVariable

请求参:参数的@RequestParam + @SpringQueryMap

请求头: 类的@RequestMapping(produce/consume/header)

​ 方法的@RequestMapping(produce/consume/header)

​ 参数的@RequestHeader

问题二:为什么选择SpringMVC注解?

SpringMVC: http 请求 -> Java 对象

open feign:Java 对象 -> http 请求

对于方法和注解信息,可以封装在新的对象中 -> 方法元数据

方法元数据的分析

1)各种参数的位置(索引)

2)参数名称,类型

3)参数类型转换器

4)编码信息

public final class MethodMetadata implements Serializable {

  private static final long serialVersionUID = 1L;
  private String configKey;
  private transient Type returnType;
  private Integer urlIndex;
  private Integer bodyIndex;
  private Integer headerMapIndex;
  private Integer queryMapIndex;
  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;
}
构造请求对象整体思路:

在这里插入图片描述

构建请求对象分两步走:

1)解析方法和注解(类、方法、参数),并把信息封装到方法元数据中 -> 应用启动

2)结合方法元数据和实际参数,构建请求对象 -> 方法调用

实参的类型转换,编码,填充

问题三:如何转换成方法元数据?

1)做成一个组件(Contract)

public interface Contract {
    // 解析接口的注解信息并封装为方法元数据的集合
  	List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

在这里插入图片描述

模板方法的设计模式

​ 接口 + 抽象实现 + 默认实现

​ 接口:提供扩展性 -> Contract

​ 抽象实现: 抽取公共逻辑 -> BaseContract

​ 默认实现:提供基本功能的使用 -> Default, SpringMvcContract

2)Contract组件从何获得?

Springboot自动装配 + 从FeignContext获取

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {

    @Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}
}

源码解读

BaseContract

解析注解的顺序:类 -> 方法 -> 参数

abstract class BaseContract implements Contract {

    /** 解析接口的注解信息并封装为方法元数据的集合 */
    @Override
    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>();
          // 获取所有public方法,包括从父接口继承而来的  
          for (Method method : targetType.getMethods()) {
            // 排除掉从Object继承的方法,static方法,接口中的default方法
            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());
    }

    /** 解析方法的注解并封装为方法元数据对象 */
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
          // 创建MethodMetadata对象
          MethodMetadata data = new MethodMetadata();

          // 设置返回值
          data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));

          // 设置configKey,方法的唯一标识: 接口名#方法名(参数类型名称1,参数类型名称2)
          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);
          }

          // 只支持GET POST等http方法
          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) {
               // 处理每个参数的注解 如果其中有一个注解属于http注解 则isHttpAnnotation为true 
               // 哪些属于http注解?如SpringMVC的@RequestHeader @PathVariable @RequestParam @SpringQueryMap
               //【关键代码】
               isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
            }
              
            if (parameterTypes[i] == URI.class) {
               data.urlIndex(i);
            } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
               // 参数类型不是URI或Options 也没有加http注解 则该参数判定为body 
               checkState(data.formParams().isEmpty(),
                  "Body parameters cannot be used with form parameters.");
               checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
               // 设置body的位置和类型【关键代码】
               data.bodyIndex(i);
               data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
            }
          }

          // ...

          return data;
    }

	/** 处理类上的注解 */
    protected abstract void processAnnotationOnClass(MethodMetadata data, Class<?> clz);

    /** 处理方法上的注解 */
    protected abstract void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method);

	/** 处理参数上的注解 */
    protected abstract boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex);
  }
SpringMvcContract

类:@RequestMapping

方法:@RequestMapping

参数:@PathVariable @SpringQueryMap @RequestHeader @RequestParam

@RequestMapping

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

   @AliasFor("path")
   String[] value() default {};

   @AliasFor("value")
   String[] path() default {};

   /**
    * The HTTP request methods to map to, narrowing the primary mapping:
    * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
    */
   RequestMethod[] method() default {};

   String[] params() default {};

   String[] headers() default {};

   /**
    * header的Content-Type
    */
   String[] consumes() default {};

   /**
	* header的Accept
    */
   String[] produces() default {};

}
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {

	private static final String ACCEPT = "Accept";

	private static final String CONTENT_TYPE = "Content-Type";

	private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor
			.valueOf(String.class);

	private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor
			.valueOf(Iterable.class);

	private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    // 参数处理器 可以自动装配也可以使用默认的处理器
	private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors;

	private final Map<String, Method> processedMethods = new HashMap<>();

	private final ConversionService conversionService;

	private final ConvertingExpanderFactory convertingExpanderFactory;

	private ResourceLoader resourceLoader = new DefaultResourceLoader();

	public SpringMvcContract(
			List<AnnotatedParameterProcessor> annotatedParameterProcessors,
			ConversionService conversionService) {
		Assert.notNull(annotatedParameterProcessors,
				"Parameter processors can not be null.");
		Assert.notNull(conversionService, "ConversionService can not be null.");

        // 初始化参数处理器
		List<AnnotatedParameterProcessor> processors;
		if (!annotatedParameterProcessors.isEmpty()) {
			processors = new ArrayList<>(annotatedParameterProcessors);
		}
		else {
			processors = getDefaultAnnotatedArgumentsProcessors();
		}
		this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
        
        // 创建参数转换器工厂 真正的转换功能来自conversionService
		this.conversionService = conversionService;
		this.convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
	}

    /** 获取默认处理器 */
	private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {

		List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
        annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor()); // 处理@MatrixVariable
		annotatedArgumentResolvers.add(new PathVariableParameterProcessor()); // 处理@PathVavirable
		annotatedArgumentResolvers.add(new RequestParamParameterProcessor()); // 处理@RequestParam
		annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); // 处理@RequestHeader
		annotatedArgumentResolvers.add(new QueryMapParameterProcessor()); // 处理@SpringQueryMap
		annotatedArgumentResolvers.add(new RequestPartParameterProcessor()); // 处理@RequestPart
        
		return annotatedArgumentResolvers;
	}
    
    @Override
	public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        // 方法先放入缓存中 表示已经处理
		this.processedMethods.put(Feign.configKey(targetType, method), method);
        
        // 调用父类的parseAndValidateMetadata
		MethodMetadata md = super.parseAndValidateMetadata(targetType, method);

        // 处理类上的RequestMapping注解
        // 因为RequestMapping注解可以加在类上和方法上 两者中注解值有优先级问题
		RequestMapping classAnnotation = findMergedAnnotation(targetType,
				RequestMapping.class);
		if (classAnnotation != null) {
			// 解析header中的produces
            // 此时可能已经从方法的RequestMapping注解获得produces的值
            // 这样处理表示方法上的RequestMapping注解优先于类上的RequestMapping注解
			if (!md.template().headers().containsKey(ACCEPT)) {
				parseProduces(md, method, classAnnotation);
			}

			// 解析header中的consumes 原理同produces
			if (!md.template().headers().containsKey(CONTENT_TYPE)) {
				parseConsumes(md, method, classAnnotation);
			}

			// 解析headers
			parseHeaders(md, method, classAnnotation);
		}
		return md;
	}
    
    /** 处理类上的注解(RequestMapping) */
	@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		if (clz.getInterfaces().length == 0) {
			RequestMapping classAnnotation = findMergedAnnotation(clz,
					RequestMapping.class);
            // 这里只处理类上RequestMapping的path,
            // 其他produces, consumes, headers放在解析方法上的RequestMapping注解之后
			if (classAnnotation != null) {
				// 如果类上的@RequestMapping有value(path) 处理后放入uri中
				if (classAnnotation.value().length > 0) {
					String pathValue = emptyToNull(classAnnotation.value()[0]);
                    // 解析path中的${} 
					pathValue = resolve(pathValue);
                    // 保证uri以/开头
					if (!pathValue.startsWith("/")) {
						pathValue = "/" + pathValue;
					}
                    // 放入uri中
					data.template().uri(pathValue);
				}
			}
		}
	}

	/** 处理方法上的注解(RequestMapping) */
	@Override
	protected void processAnnotationOnMethod(MethodMetadata data,
			Annotation methodAnnotation, Method method) {
        // 如果不是@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");
		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);
				if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
					pathValue = "/" + pathValue;
				}
				data.template().uri(pathValue, true);
			}
		}

		// 解析header中的produces
		parseProduces(data, method, methodMapping);

		// 解析header中的consumes
		parseConsumes(data, method, methodMapping);

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

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

	/** 处理参数上的注解 */
	@Override
	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;
				processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
						parameterAnnotation, method, paramIndex);
                // 参数处理器处理【关键代码】
				isHttpAnnotation |= processor.processArgument(context,
						processParameterAnnotation, method);
			}
		}

        // 如果是http注解并且没有对应的expander
        // 什么expander -> 参数转换器
		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;
	}
    // ...
}
AnnotatedParameterProcessor

PathVariableParameterProcessor:@PathVariable 解析路径参数

QueryMapParameterProcessor: @SpringQueryMap 解析请求参数

RequestHeaderParameterProcessor: @RequestHeader 解析请求头

RequestParamParameterProcessor:@RequestParam 解析请求参数

MatrixVariableParameterProcessor: @MatrixVariable 解析矩阵参数

RequestPartParameterProcessor: @RequestPart 解析form表单 File文件

QueryMapParameterProcessor 与 RequestParamParameterProcessor的区别:

前者可以解析自定义实体对象,Map和基本类型,没有特别的限制

后者只能解析Map和基本类型不能解析自定义对象类型

QueryMapParameterProcessor
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {

	private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class;

	@Override
	public Class<? extends Annotation> getAnnotationType() {
		return ANNOTATION;
	}

	@Override
	public boolean processArgument(AnnotatedParameterContext context,
			Annotation annotation, Method method) {
		int paramIndex = context.getParameterIndex();
		MethodMetadata metadata = context.getMethodMetadata();
        // 对@SpringQueryMap注解所对应的参数的类型没有限制
		if (metadata.queryMapIndex() == null) {
			metadata.queryMapIndex(paramIndex);
			metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
		}
		return true;
	}
}
RequestParamParameterProcessor
public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {

	private static final Class<RequestParam> ANNOTATION = RequestParam.class;

	@Override
	public Class<? extends Annotation> getAnnotationType() {
		return ANNOTATION;
	}

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

        // 参数必须是Map类型 否则不可以成为QueryMap
		if (Map.class.isAssignableFrom(parameterType)) {
			checkState(data.queryMapIndex() == null,
					"Query map can only be present once.");
			data.queryMapIndex(parameterIndex);

			return true;
		}

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

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

实参类型转换和填充

interface Expander {

    /**
     * Expands the value into a string. Does not accept or return null.
     */
    String expand(Object value);
}
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {

    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor
			.valueOf(String.class);
    
	private static class ConvertingExpanderFactory {

		private final ConversionService conversionService;

		ConvertingExpanderFactory(ConversionService conversionService) {
			this.conversionService = conversionService;
		}

		Param.Expander getExpander(TypeDescriptor typeDescriptor) {
			return value -> {
				Object converted = this.conversionService.convert(value, typeDescriptor,
						STRING_TYPE_DESCRIPTOR);
				return (String) converted;
			};
		}

	}
}

Java 中的所有类型
raw type:原始类型,对应 Class 即我们通常说的引用类型,包括普通的类,例如 String.class、List.class 也包括数组(Array.class)、接口(Cloneable.class)、注解(Annotation.class)、枚举(Enum.class)等

primitive types:基本类型,对应 Class 包括 Built-in 内置类型,例如 int.class、char.class、void.class 也包括 Wrappers 内置类型包装类型,例如 Integer.class、Boolean.class、Void.class

parameterized types:参数化类型,对应 ParameterizedType 带有类型参数的类型,即常说的泛型,例如 List、Map<Integer, String>、List<? extends Number> 实现类 sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

type variables:类型变量类型,对应 TypeVariable即参数化类型 ParameterizedType 中的 E、K 等类型变量,表示泛指任何类实现类 sun.reflect.generics.reflectiveObjects.TypeVariableImpl

array types:泛型数组类型,对应 GenericArrayType元素类型是参数化类型或者类型变量的泛型数组类型,例如 T[]实现类 sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl
Type 接口的另一个子接口 WildcardType 代表通配符表达式类型,或泛型表达式类型,比如?、? super T、? extends T,他并不是 Java 类型中的一种。

private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {

    private final QueryMapEncoder queryMapEncoder;

    protected final MethodMetadata metadata;
    private final Map<Integer, Expander> indexToExpander = new LinkedHashMap<Integer, Expander>();

    /** 通过metadata信息和实参创建RequestTemplate */
    @Override
    public RequestTemplate create(Object[] argv) {
        
      // 把metadata中的半成品template拷贝一份  
      RequestTemplate mutable = RequestTemplate.from(metadata.template());
        
      // 处理URI对象
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.target(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);
        
      // 处理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);
      }

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

      return template;
    }
    

    @SuppressWarnings("unchecked")
    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;
    }

    @SuppressWarnings("unchecked")
    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()
                    : UriUtils.encode(nextObject.toString()));
          }
        } else {
          values.add(currValue == null ? null
              : encoded ? currValue.toString() : UriUtils.encode(currValue.toString()));
        }

        mutable.query(encoded ? currEntry.getKey() : UriUtils.encode(currEntry.getKey()), values);
      }
      return mutable;
    }

    // ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1260357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【华为OD题库-040】计算最接近的数-java

题目 给定一个数组X和正整数K&#xff0c;请找出使表达式X[i]-x[i1]…-X[ik-1]&#xff0c;结果最接近于数组中位数的下标i&#xff0c;如果有多个满足条件&#xff0c;请返回最大的i。 其中&#xff0c;数组中位数:长度为N的数组&#xff0c;按照元素的值大小升序排列后&#…

华纳云:linux中怎么实现apache安装与配置

在 Linux 系统中&#xff0c;安装和配置 Apache HTTP 服务器通常涉及以下步骤。以下以 Ubuntu 为例&#xff0c;其他 Linux 发行版的步骤也大致相同。 步骤 1&#xff1a;安装 Apache 打开终端并运行以下命令&#xff1a; sudo apt update sudo apt install apache2 步骤 …

魏副业而战:视频号副业项目赚钱攻略,每天30分钟,日入500+

我是魏哥&#xff0c;与其躺平&#xff0c;不如魏副业而战&#xff01; 最近很多团队在操作视频号分成计划项目。 说真的&#xff0c;这个副业项目很不错&#xff0c;魏哥也操作测试一下&#xff0c;每天收益大几百&#xff0c;收益如下&#xff1a; 大家看了&#xff0c;是不…

如何把视频中不需要的人物去掉?

从视频中移除不想要的对象或区域&#xff0c;这项工作以前既繁琐复杂又很消耗时间。但使用“AI智能抠像”工具&#xff0c;只需几个简单的步骤&#xff0c;即可轻松移除视频中任何不想要的人物。 在制作视频的过程中&#xff0c;我们常常会遇到需要将视频中多余的人物去掉的情…

Redis深入理解-三次握手、槽位机制

Redis 节点之间的三次握手原理分析 比如多台 Redis 之间要建立集群&#xff0c;那么连接其中的一台 Redis 客户端&#xff0c;向其他 Redis 发送 meet 命令即可通知其他节点&#xff0c;那么发送 meet 命令给其他节点后&#xff0c;对方也会在内存中创建一个 ClusterNode 结构…

无代码未来:智能、可视化、自动化的融合

无代码是一个相对较新的概念&#xff0c;不同的人群对其界定可能存在一定的差异。 对于IT专业人士和开发人员而言&#xff0c;无代码通常是指使用可视化界面和拖拽操作来构建应用程序的工具和平台。 无代码平台通过提供预先构建的组件和模块&#xff0c;使得开发人员可以通过简…

Shader编程:“热成像”风格的效果是怎么实现的?(内附源码)

未经作者(微信ID:Byte-Flow)允许,禁止转载 文章首发于公众号:字节流动 之前转载过知乎上面的一篇文章: 作者:这是上帝的杰作 链接:https://zhuanlan.zhihu.com/p/344110917 文章详细讲解了 Shader 实现“热成像”效果的思路,但是并没有给出完整的实现代码,后台有读者…

Fluent热辐射壁面设置

对于固体壁面&#xff0c;可分为&#xff1a; 内部面外部面 外部面&#xff0c;若需要考虑外部热辐射的影响&#xff0c;需要将类型改为“mixed”或者“radiation”类型&#xff0c;并设置外部的发射率。 内部面通常为“wall”和“wall-shadow”的配对形式。 对于两侧均是透明…

经验分享:JMeter控制RPS

一、前言 ​ RPS (Request Per Second)一般用来衡量服务端的吞吐量&#xff0c;相比于并发模式&#xff0c;更适合用来摸底服务端的性能。我们可以通过使用 JMeter 的常数吞吐量定时器来限制每个线程的RPS。对于RPS&#xff0c;我们可以把他理解为我们的TPS&#xff0c;我们就…

工博会新闻稿汇总

23届工博会媒体报道汇总 点击文章标题即可进入详情页 9月23日&#xff0c;第23届工博会圆满落幕&#xff01;本届工博会规模之大、能级之高、新展品之多创下历史之最。高校展区在规模、能级和展品上均也创下新高。工博会系列报道深入探讨了高校科技发展的重要性和多方面影响。…

疯狂小杨哥花3000万举办演唱会

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 确实厉害&#xff0c;95年的&#xff0c;白手起家&#xff0c;1亿元买楼&#xff0c;3000万办演唱会&#xff0c;而且会在线上直播&#xff0c;疯狂小杨哥正常从“网红”向“企业家”的转变。全网没…

HarmonyOS 应用模型开发指南介绍

一、基本概念解析 新版文档中的知识点&#xff0c;介绍更全面&#xff0c;逻辑更清晰&#xff0c;提供了各类基本概念解析&#xff0c;帮助开发者更快学习、掌握系统能力。以下是新版文档部分概念展示。 1、HAP是什么&#xff1f; 开发者通过DevEco Studio把应用程序编译为一…

C++之哈希

unordered系列容器的效率之所以比较高(尤其是查找),是因为它底层使用了哈希结构,即哈希表. 哈希概念 前言: 顺序结构以及平衡树中, 元素关键码与其存储位置之间没有对应的关系, 因此在查找一个元素 时, 必须要经过关键码的多次比较. 顺序查找时间复杂度为O(N), 平衡树中为树的…

内蒙古珠三角服务工作站挂牌 搭建桥梁促进民营经济发展

内蒙古自治区促进民营经济发展珠三角服务工作站挂牌仪式暨蒙粤两地民营企业家交流座谈会在深圳市顺利举行。 为贯彻落实内蒙古自治区党委、政府关于促进民营经济发展的决策部署&#xff0c;11月26日&#xff0c;由自治区发展改革委、工商联共同主办的“内蒙古自治区促进民营经济…

SQL Server详细使用教程(包含启动SQL server服务、建立数据库、建表的详细操作) 非常适合初学者

文章目录 目录 前言 一、启动SQL server服务的三种方法 1.不启动SQL server服务的影响 2.方法一&#xff1a;利用cmd启动SQL server服务 3.方法二&#xff1a;利用SQL Server配置管理器启动SQL server服务 4.方法三&#xff1a;在服务管理器中启动SQL server服务 二、建立数据库…

js的数组去重方法

目录 es6数组中对象去重 1. filter()用法 2. findIndex()用法 3. 去重 其他方法&#xff1a; 方法二&#xff1a;reduce()去重 1. reduce()用法 1.1 找出字符长度最长的数组成员。 1.2 扁平化二维数组 1.3 扁平化多维数组 三、总结方案&#xff1a; 使用Set&#xf…

华为的数字化转型(9)——企业架构4A集成模型

企业架构&#xff08;Enterprise Architecture&#xff0c;EA&#xff09;是衔接战略与项目实施的桥梁&#xff0c;引入企业架构方法&#xff0c;可以对数字化转型愿景进行系统性的、分层分级的梳理和解释&#xff0c;以便企业上下在同一张蓝图上统一认识。 企业架构提供了整体…

Intel Software Guard Extensions简介

文章目录 前言一、新的基于硬件的控件实现数据安全二、机密计算的挑战三、用于机密计算的增强安全功能四、Enclave验证和数据密封五、数据中心认证参考资料 前言 最近开始研究Intel SGX硬件特性&#xff0c;记录下研究过程。 参考文档&#xff1a;product-brief-SGX 一、新的…

如何与死锁斗争!!!

其他系列文章导航 Java基础合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、死锁场景现场 二、死锁是如何产生的 三、死锁排查思路 四、sql模拟死锁复现 五、死锁的解决方案 前言 为避免影响业务&#xff0c;应尽可能避…

springboot实现数据脱敏

springboot实现数据脱敏 怎么说呢&#xff0c;写着写着发觉 ”这写的什么玩意“ 。 总的来说就是&#xff0c;这篇文章并不能解决数据脱敏问题&#xff0c;但以下链接可以。 SpringBoot中利用自定义注解优雅地实现隐私数据脱敏 然后回到本文&#xff0c;本来是想基于AOP代理&am…