spring cloud 使用 Feign 进行服务调用时,不支持对象参数。
通常解决方法是,要么把对象每一个参数平行展开,并使用 @RequestParam 标识出每一个参数,要么用 @RequestBody 将请求改为 body 传参,虽然这样解决了问题,但是这样限制了传参方式,并且使代码变得很繁重。
以下为完美解决 Feign 对象传参问题的办法。
1. 引入如下依赖(可以在maven仓库中搜索 strongfeign)
1 <!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient --> 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。
注意:不要使用 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") 同时使用在不同参数上。