springcloud feign 解决多对象入参的情况

只愿长相守 提交于 2019-12-29 13:20:49

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

背景

业务分割创建一个新的项目,希望引用新的技术栈(使用springcloud 全家桶,因项目原因暂时无法升级到springboot2.0 所以这里rpc使用feign),但还是沿用之前的rpc接口入参(原rpc框架为thrift)

  • 出现的问题 原rpc接口存在多个对象入参的情况

下面演示如何使用feign解决多对象入参的情况

这里使用的是SpringMvcContract feign.Contract.Default 风格变化太大暂时未使用 也会介绍此契约传多个对象的情况

注意

因为入参格式改变了,对应的提供者也需要做出对应的参数解析 可以在提供者自定义注解解析参数,网上有大量案例

SpringMvcContract

支持springmvc中那样使用注解 这里只列举参数上的注解

@PathVariable @RequestHeader @RequestParam feign对以上参数进行了兼容 若没有使用注解 默认是Body parameters

三个注解分别实现了AnnotatedParameterProcessor接口对应的类如下

  • PathVariableParameterProcessor
  • RequestHeaderParameterProcessor
  • RequestParamParameterProcessor

下面是对参数注解的解析

feign.Contract.BaseContract#parseAndValidateMetadata
processAnnotationsOnParameter在SpringMvcContract重写并处理AnnotatedParameterProcessor子类
/**
     * Called indirectly by {@link #parseAndValidatateMetadata(Class)}.
     */
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
      ......省略代码
      int count = parameterAnnotations.length;
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }
        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation) {
          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]));
        }
      }
      ......省略代码

      return data;
    }

以上是对feign处理springmvc注解的一个大概介绍

如何解决feign多入参的问题

feign 没有提供多余的注解 我们将自定义注解

自定义注解CustomRequestParam

/**
 * copy @RequestParam
 *
 * @author sunmingji
 * @date 2019-12-27
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestParam {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean required() default true;

	String defaultValue() default ValueConstants.DEFAULT_NONE;

}

实现AnnotatedParameterProcessor

@Slf4j
public class CustomRequestParamParameterProcessor implements AnnotatedParameterProcessor {

	private static final Class<CustomRequestParam> ANNOTATION = CustomRequestParam.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();

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

			return true;
		}

		CustomRequestParam requestParam = ANNOTATION.cast(annotation);
		String name = requestParam.value();
		checkState(emptyToNull(name) != null,
				"CustomRequestParam.value() was empty on parameter %s",
				parameterIndex);
		context.setParameterName(name);
		
		/*
			这里视情况而定 参数放在请求头后面的话 使用data.template().query(name, query)
			放在请求体中使用data.formParams().add(name);
			因为我们存放的是对象需要制定Expander 下面有参考的源码
			data.indexToExpander().put(context.getParameterIndex(), new JsonStrExpander());
		*/
		
		//参数放在请求头及在url后面
//		Collection<String> query = context.setTemplateParameter(name,
//				data.template().queries().get(name));
//		data.template().query(name, query);

		//参数放在请求体中
		//参考 feign.Contract.Default.processAnnotationsOnParameter
		/**
			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;
			  String varName = '{' + name + '}';
			  if (!data.template().url().contains(varName) &&
				  !searchMapValuesContainsSubstring(data.template().queries(), varName) &&
				  !searchMapValuesContainsSubstring(data.template().headers(), varName)) {
				data.formParams().add(name);
			  }
			}
		 */
		data.formParams().add(name);

		data.indexToExpander().put(context.getParameterIndex(), new JsonStrExpander());
		return true;
	}
}

配置Contract

要把自带的几个注解加上去

@Configuration
@Slf4j
public class FeignSupportConfig {

	@Bean
	public Contract feignContract() {

		List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();

		annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
		annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
		annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
		annotatedArgumentResolvers.add(new CustomRequestParamParameterProcessor());
		return new SpringMvcContract(annotatedArgumentResolvers);
	}
}

声明接口

@FeignClient(value = "cloud-zuul", configuration = FeignSupportConfig.class)
public interface IUserService {

    @RequestMapping(value = "api-a/getUser", method = RequestMethod.POST, consumes = "application/json")
    String getUserComplex(@RequestParam("user") User user, @RequestParam("dept") Dept dept, @RequestParam("schoolId") String schoolId,
                          @CustomRequestParam("userArray") User[] userArray);

    @RequestMapping(value = "api-a/getUser", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    String getUserComplex_(@RequestParam("user") User user, @RequestParam("dept") Dept dept, @RequestParam("schoolId") String schoolId,
                        @CustomRequestParam(value = "userArray") User[] userArray, @CustomRequestParam(value = "deptList") List<Dept> deptList);


    @RequestMapping(value = "api-a/getUser", method = RequestMethod.POST, consumes = "application/json")
    String getUserRequestBody(@CustomRequestParam("userArray") User[] userArray);

    @RequestMapping(value = "api-a/getUser", method = RequestMethod.POST, consumes = "application/json")
    String getUserRequestBody_(@RequestBody User[] userArray2);
}

提供者的报文

@RequestParam注解的参数在url后面 @CustomRequestParam注解的参数在body中 使用@RequestParam在对象需要重写toString 但是对于List不能重写需要

2019-12-29 12:51:31.518 DEBUG 61973 --- [nio-8086-exec-2] o.a.coyote.http11.Http11InputBuffer      : Received [POST /getUser?user=%7B%22userId%22%3A%22userId%22%2C%22userName%22%3A%22userName%22%7D&dept=%7B%22deptId%22%3A%22deptId%22%2C%22deptName%22%3A%22deptName%22%7D&schoolId=schoolIdParam HTTP/1.1
content-type: application/json;charset=UTF-8
accept: */*
user-agent: Java/1.8.0_171
x-forwarded-host: 192.168.199.108:8007
x-forwarded-proto: http
x-forwarded-prefix: /api-a
x-forwarded-port: 8007
x-forwarded-for: 192.168.199.108
Accept-Encoding: gzip
Content-Length: 132
Host: 192.168.199.108:8086
Connection: Keep-Alive

{"userArray":"[{\"userId\":\"userId\",\"userName\":\"userName\"}]","deptList":["{\"deptId\":\"deptId\",\"deptName\":\"deptName\"}"]}]

feign.Contract.Default

使用feign.Contract.Default

声明接口

@FeignClient(value = "cloud-zuul", configuration = FeignSupportConfig.class)
public interface IUserService {

    @RequestLine("POST /api-a/getUser")
    @Headers("Content-Type: application/json")
    // json curly braces must be escaped!
    // 这里JSON格式需要的花括号居然需要转码
    @Body("%7B\"user\": {user}, \"dept\": {dept}, \"userArray\": {userArray}, \"deptList\": {deptList}%7D")
    String json(@Param("user") User user, @Param("dept") Dept dept,
                @Param(value = "userArray", expander = JsonStrExpander.class) User[] userArray, @Param("deptList") List<Dept> deptList);
}

配置Contract

@Configuration
@Slf4j
public class FeignSupportConfig {

	@Bean
	public Contract feignContract() {

		
		return new feign.Contract.Default();
	}
}

Param.Expander

public class JsonStrExpander implements Param.Expander {
	@Override
	public String expand(Object value) {
		return JSON.toJSONString(value);
	}
}

总结

具体使用哪种Contract 是情况而定 在声明接口的时候可以指定配置 @FeignClient(value = "cloud-zuul", configuration = FeignSupportConfig.class)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!