前言
最近在研究 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,有什么不妥的地方還希望各位大蝦指正。