spring cloud 使用 Feign 進行服務調用時,不支持對象參數。
通常解決方法是,要么把對象每一個參數平行展開,並使用 @RequestParam 標識出每一個參數,要么用 @RequestBody 將請求改為 body 傳參,雖然這樣解決了問題,但是這樣限制了傳參方式,並且使代碼變得很繁重。
以下為完美解決 Feign 對象傳參問題的辦法。
1. 引入如下依賴(可以在maven倉庫中搜索 strongfeign)
1 2 <dependency> 3 <groupId>com.moonciki.strongfeign</groupId> 4 <artifactId>feign-httpclient</artifactId> 5 <version>10.2.3</version> 6 </dependency>
該源碼修改自 https://github.com/OpenFeign/feign,提交過pr,但是項目原作者並沒有采納,pr地址如下:https://github.com/OpenFeign/feign/pull/949
之后為了同步到了maven 倉庫,做了相應刪減及pom的變更,具體改動可參考github。地址:https://github.com/cdmamata/strong-feign
注意:不要使用 10.3.x版本,該版本有問題。如果jar包無法下載請使用 maven 中央倉庫。
2. 創建如下三個類
開始時,打算把以下三個類加進倉庫中,但由於如下三個類內容不多,並且有很多定制化的可能,因此單獨實現。
2.1 ParamModel.java
1 package com.moonciki.strongfeign.model.annotation; 2 3 import java.lang.annotation.*; 4 5 @Target({ElementType.PARAMETER}) 6 @Retention(RetentionPolicy.RUNTIME) 7 @Documented 8 public @interface ParamModel { 9 String value() default ""; 10 }
2.2 ModelExpander.java
1 package com.moonciki.strongfeign.model.expander; 2 3 import com.alibaba.fastjson.JSON; 4 import feign.Param; 5 import lombok.extern.slf4j.Slf4j; 6 7 import java.util.Map; 8 9 @Slf4j 10 public class ModelExpander implements Param.Expander { 11 12 public String expand(Object value) { 13 String objectJson = JSON.toJSONString(value); 14 return objectJson; 15 } 16 17 @Override 18 public String expandWithName(Object value, String name) { 19 String valueExpand = null; 20 21 if(value != null){ 22 if(name != null) { 23 try { 24 Map<String, Object> jsonMap = (Map<String, Object>)JSON.toJSON(value); 25 26 Object getValue = jsonMap.get(name); 27 if(getValue != null){ 28 valueExpand = getValue.toString(); 29 } 30 } catch (Exception e) { 31 log.error("GET VALUE ERROR:", e); 32 } 33 }else { 34 valueExpand = value.toString(); 35 } 36 } 37 38 return valueExpand; 39 } 40 }
注:該類需依賴 fastjson,也可根據個人需要修改該方法。
2.3 ParamModelParameterProcessor.java
1 package com.moonciki.strongfeign.model.processor; 2 3 import com.moonciki.strongfeign.model.annotation.ParamModel; 4 import com.moonciki.strongfeign.model.expander.ModelExpander; 5 import feign.MethodMetadata; 6 import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; 7 8 import java.lang.annotation.Annotation; 9 import java.lang.reflect.Field; 10 import java.lang.reflect.Method; 11 import java.util.Collection; 12 13 14 public class ParamModelParameterProcessor implements AnnotatedParameterProcessor { 15 16 private static final Class<ParamModel> ANNOTATION = ParamModel.class; 17 18 public Class<? extends Annotation> getAnnotationType() { 19 return ANNOTATION; 20 } 21 22 @Override 23 public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { 24 25 int parameterIndex = context.getParameterIndex(); 26 Class parameterType = method.getParameterTypes()[parameterIndex]; 27 MethodMetadata data = context.getMethodMetadata(); 28 29 Field[] fields = parameterType.getDeclaredFields(); 30 31 for(Field field: fields) { 32 String name = field.getName(); 33 context.setParameterName(name); 34 35 Collection query = context.setTemplateParameter(name, (Collection)data.template().queries().get(name)); 36 data.template().query(name, query); 37 } 38 data.indexToExpander().put(context.getParameterIndex(), new ModelExpander()); 39 40 return true; 41 } 42 }
3. 使用注解配置 feign Contract 對象
1 @Bean 2 public Contract feignContract(){ 3 List<AnnotatedParameterProcessor> processors = new ArrayList<>(); 4 processors.add(new ParamModelParameterProcessor()); 5 processors.add(new PathVariableParameterProcessor()); 6 processors.add(new RequestHeaderParameterProcessor()); 7 processors.add(new RequestParamParameterProcessor()); 8 return new SpringMvcContract(processors); 9 }
4. 使用方法示例
1 @Primary 2 @FeignClient(value = "/user", fallback = UserClientFallback.class) 3 public interface UserClient { 4 5 /** 6 * demo post 7 * @return 8 */ 9 @PostMapping("/demoPost") 10 Result demoPost(@ParamModel UserAccount userAccount); 11 12 /** 13 * demo get 14 * @return 15 */ 16 @GetMapping("/demoGet") 17 Result demoPost(@ParamModel UserAccount userAccount); 18 19 20 }
使用時,只需要在對象前加 @ParamModel 注解即可
需要同時傳遞對象及基本類型參數時, @ParamModel 可以與 @RequestParam("jobName") 同時使用在不同參數上。