From @wanghongfei on August 12, 2016 4:14

I'm using Feign with Spring Cloud Netflix stack. In SpringMVC we can use POJO as request parameter which is very convenient:

@RequestMapping(value = "/group/list")
    public List<AdvertGroupVO> list(AdvertGroupVO vo) throws AlanException {

        return adGroupService.list(vo);
    }

But in Feign I have to write lots of @RequestParam:

@FeignClient(name = "fooService")
public interface foo {

    @RequestMapping(value = "/group/list")
    public List<AdvertGroupVO> list(@RequestParam Integer id,
                                      @RequestParam String name,
                                      @RequestParam ... ...);
}

I think it good for feign's user. Thanks!

Copied from original issue: OpenFeign/feign#438

Comment From: codefromthecrypt

In feign's DefaultContract, a parameter without any annotations is assumed to be processed by feign's Encoder, which can do what's been requested here. We can decide if this behavior makes sense or not.

Comment From: wanghongfei

Thanks for @adriancole 's reply. In current Spring Cloud Netflix Feign's implementation it seems feign doesn't encode POJO to HTTP request parameter by default, or there's something I do not know yet

Comment From: codefromthecrypt

@wanghongfei are you saying that you want something to literally write each field as a query parameter (ex via reflection)? If so, that would be a custom encoder and I don't think it is a high-reuse thing at this point.

Comment From: codefromthecrypt

closest thing in feign upstream is @QueryMap which explodes a map into query parameters. I'm not sure if that is supported in MVC @RequestParam

Comment From: wanghongfei

@adriancole Thanks, I'll try @QueryMapor define my own encoder.

Comment From: wongloong

@wanghongfei 请问这个问题你最后怎么解决的?

Comment From: asarkar

@adriancole @wanghongfei My PR https://github.com/spring-cloud/spring-cloud-netflix/pull/1361 allows @RequestParam to be used without a value/name and with a Map. Not with a POJO, but that's the closest thing to POJO.

Comment From: barrer

@wongloong supporting POJO as request parameter solution: http://www.itmuch.com/spring-cloud-sum/feign-multiple-params/

Let's talk about how to use Feign to construct a POST request with multiple parameters. Assume that the Service Provider's Controller is written as follows:
@RestController
public class UserController {
  @PostMapping("/post")
  public User post(@RequestBody User user) {
    ...
  }
}
How do we use Feign to request for it? The answer is very simple, example:
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
  @RequestMapping(value = "/post", method = RequestMethod.POST)
  public User post(@RequestBody User user);
}

tips: method = RequestMethod.POST and @RequestBody I tested successfully!

Comment From: spencergibb

Right, this issue is for a GET method, not post.

Comment From: SeauWong

yml:

feign:
  httpclient:
    enabled: true

MAVEN:

            <!--httpClient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.3</version>
            </dependency>

            <dependency>
                <groupId>com.netflix.feign</groupId>
                <artifactId>feign-httpclient</artifactId>
                <version>8.17.0</version>
            </dependency>

SendPoint(get method):

@RequestMapping(value = "/user/list",method = RequestMethod.GET,consumes = "application/json")
    List<User> users(@RequestBody QueryBean queryBean);

ReceivePoint(post method):

@RequestMapping(value = "/user/list",method = RequestMethod.POST,consumes = "application/json")
    List<User> users(@RequestBody QueryBean queryBean){
                      func();
}

Comment From: charlesvhe

I use RequestInterceptor to solve this problem:

public class YryzRequestInterceptor implements RequestInterceptor {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void apply(RequestTemplate template) {
        // feign 不支持 GET 方法传 POJO, json body转query
        if (template.method().equals("GET") && template.body() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.body());
                template.body(null);

                Map<String, Collection<String>> queries = new HashMap<>();
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) {   // 叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) {   // 数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else {  // 根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}

Comment From: rmfish

Create a custom SpringMvcContract bean which override processAnnotationsOnParameter method:

https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10

Comment From: ryanjbaxter

I dont think this is still an issue.

Comment From: eefnrowe

@charlesvhe ^_^

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Greenwich.RC2</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

   <!--feign相关-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>10.1.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
        <version>10.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
    </dependency>
</dependencyManagement>

@Configurable
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    @Autowired
    private ObjectMapper objectMapper;

    public FeignBasicAuthRequestInterceptor() {}

    @Override
    public void apply(RequestTemplate template) {
        ///**get-pojo贯穿*/
        if (template.method().equals("GET") && template.requestBody().asBytes() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.requestBody().asBytes());
                //template.body(null);
                Map<String, Collection<String>> queries = new HashMap<>();
                //feign 不支持 GET 方法传 POJO, json body转query
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //处理 get-pojo贯穿
    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) { //叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) { //数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else { //根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}

server1 - feignRemote:

@PostMapping("/app")
Response<Integer> appInsert(@RequestBody AppDTO dto);

@DeleteMapping("/app/{appId}")
Response<Integer> appDelete(@PathVariable("appId") String appId);

@PutMapping("/app")
Response<Integer> appUpdate(@RequestBody AppDTO dto);

@GetMapping("/app/{appId}")
Response<AppDTO> appGet(@PathVariable("appId") String appId);

@GetMapping("/app")
Response<List<AppDTO>> appList(AppQTO qto);

@GetMapping("/app/listcount")
Response<List<AppDTO>> appListCount(AppQTO qto);

server2 - controller

@PostMapping
public Response<Integer> insert(@RequestBody AppDTO dto) {
    System.out.println("#######insert:"+ JsonUtil.toJson(dto));
    return new Response<>(appService.insert(dto));
}

@DeleteMapping("/{appId}")
public Response<Integer> delete(@PathVariable("appId") String appId) {
    return new Response<>(appService.deleteByIds(appId));
}

@PutMapping
public Response<Integer> update(@RequestBody AppDTO dto) {
    System.out.println("#######update:"+ JsonUtil.toJson(dto));
    return new Response<>(appService.update(dto));
}

@GetMapping("/{appId}")
public Response<AppDTO> get(@PathVariable("appId") String appId) {
    return new Response<>(appService.getObject(appId));
}

@GetMapping
public Response<List<AppDTO>> list(AppQTO qto) {
    System.out.println("#######list:"+ JsonUtil.toJson(qto));
    return new Response<>(appService.listObjects(qto));
}

@GetMapping("/listcount")
public Response<List<AppDTO>> listCount(AppQTO qto) {
    System.out.println("#######listCount:"+ JsonUtil.toJson(qto));
    return appService.list(qto);
}

Comment From: LUCKYZHOUSTAR

@eefnrowe thanks

Comment From: maketubo

@eefnrowe With java1.8 sun.net.www.protocol.http.HttpURLConnection or feign-okhttp 10.4.0, I need to empty requestBody like template.body(Request.Body.empty()); after template.queries(queries); then work fine. 我看到你那边把template.body(null);注释掉了是因为之前版本的httpclient没问题?

Comment From: cbjjensen

@maketubo I changed it to

        byte[] emptyBody = Request.Body.empty().asBytes();
        if (template.method().equals("GET") && !Arrays.equals(template.body(), emptyBody)) {
        //....
             template.body(Request.Body.empty());
        }