[spring cloud feign] [bug] 使用对象传输get请求参数

前言

最近在研究 srping cloud feign ,遇到了一个问题,就是当 get 请求 的参数使用对象接收时,就会进入熔断返回。经过百度,发现网上大部分的解决方案都是将请求参数封装到RequestBody里面进行传输。但感觉这种方式并不怎么优雅。所以自己就研究了研究,以下是我给出的方案。有什么不对的地方还希望大家指正。

环境

  • java版本:8
  • spring cloud:Finchley.RELEASE

解决方案

1. 首先我们创建一个注解 GetParam ,用于将参数相关的信息封装到 RequestTemplate 。

import java.lang.annotation.*;

/**
 * Created by qingyun.yu on 2018/9/4.
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetParam {
    Class value() default Object.class;
}

2. 创建注解处理器 GetParamParameterProcessor ,并且注册到spring容器中,主要逻辑是将参数相关信息封装到 Template 。

import feign.MethodMetadata;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;

/**
 * Created by qingyun.yu on 2018/9/4.
 */
@Component
public class GetParamParameterProcessor implements AnnotatedParameterProcessor {
    private static final Class<GetParam> ANNOTATION = GetParam.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();
        Field[] fields = parameterType.getDeclaredFields();
        for(Field field: fields) {
            String name = field.getName();
            context.setParameterName(name);
            Collection query = context.setTemplateParameter(name, (Collection)data.template().queries().get(name));
            data.template().query(name, query);
        }
        return true;
    }
}

3.  创建 FeignConfig ,用于将Spring的参数注解处理器注册到Spring中。

import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor;
import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor;
import org.springframework.cloud.openfeign.annotation.RequestParamParameterProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by qingyun.yu on 2018/9/4.
 */
@Configuration
public class FeignConfig {
    @Bean
    public PathVariableParameterProcessor getPathVariableParameterProcessor() {
        return new PathVariableParameterProcessor();
    }

    @Bean
    public RequestParamParameterProcessor getRequestParamParameterProcessor() {
        return new RequestParamParameterProcessor();
    }

    @Bean
    public RequestHeaderParameterProcessor getRequestHeaderParameterProcessor() {
        return new RequestHeaderParameterProcessor();
    }
}

4.修改 io.github.openfeign:feign-core 的源码,在 ReflectiveFeign 类中增加如下代码,源码地址点这里,也可以直接用我的demo里面的代码。

private boolean isGetUrlParam(Object value, RequestTemplate mutable) {
      if(mutable.method() != "GET") {
        return false;
      }
      switch (value.getClass().getSimpleName()) {
        case "Integer":
          return false;
        case "String":
          return false;
        case "Boolean":
          return false;
        case "Float":
          return false;
        case "Long":
          return false;
        case "Character":
          return false;
        case "Double":
          return false;
        case "Byte":
          return false;
        case "Short":
          return false;
        case "Date":
          return false;
        case "BigDecimal":
          return false;
        default:
          System.out.println("value is object param");;
      }
      return true;
    }

    private Map<String, Object> getObjectParam(Object obj) {
      Field[] fields = obj.getClass().getDeclaredFields();
      Map<String, Object> urlParams = new HashMap<>();
      for (Field field : fields) {
        field.setAccessible(true);
        try {
          Object value = field.get(obj);
          if (value == null) {
            urlParams.put(field.getName(), "");
          } else {
            urlParams.put(field.getName(), value);
          }
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
      return urlParams;
    }

并且将 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()];

        Map<String, Object> urlParams = null;
        if (isGetUrlParam(value, mutable)) {
          urlParams = getObjectParam(value);
        }

        if (value != null) { // Null values are skipped.
          if (indexToExpander.containsKey(i)) {
            value = expandElements(indexToExpander.get(i), value);
          }
          for (String name : entry.getValue()) {
            if (isGetUrlParam(value, mutable)) {
              varBuilder.put(name, urlParams.get(name));
            } else {
              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;
    }

5. 编译打包 feign-core ,并且将其引入工程里面。

<dependency>
    <groupId>com.yun.demo</groupId>
    <artifactId>feign-core</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

注意要将spring cloud的原本引入的 feign-core 去除掉。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<exclusions>
<exclusion>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</exclusion>
</exclusions>
</dependency>

6. 使用时将注解注入到get请求的对象上就可以了。

import com.yun.demo.annotation.GetParam;
import com.yun.demo.entity.User;
import com.yun.demo.fallback.UserClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * Created by qingyun.yu on 2018/9/2.
 */
@FeignClient(name = "feign-service", fallback = UserClientFallback.class)
public interface UserClient {

    @RequestMapping(value = "user", method = RequestMethod.GET)
    User getUser(@GetParam User user);

}

下面附上我的git地址,里面是我写的demo,有什么不妥的地方还希望各位大虾指正。

猜你喜欢

转载自www.cnblogs.com/cafebabe-yun/p/9588323.html