Skip to content

Suggest supporting POJO as request parameter #1253

Closed
@codefromthecrypt

Description

@codefromthecrypt

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

Activity

codefromthecrypt

codefromthecrypt commented on Aug 12, 2016

@codefromthecrypt
Author

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.

wanghongfei

wanghongfei commented on Aug 12, 2016

@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

codefromthecrypt

codefromthecrypt commented on Aug 12, 2016

@codefromthecrypt
Author

@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.

codefromthecrypt

codefromthecrypt commented on Aug 12, 2016

@codefromthecrypt
Author

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

wanghongfei

wanghongfei commented on Aug 12, 2016

@wanghongfei

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

wongloong

wongloong commented on Dec 8, 2016

@wongloong

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

asarkar

asarkar commented on Jun 25, 2017

@asarkar
Contributor

@adriancole @wanghongfei My PR #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.

barrer

barrer commented on Aug 18, 2017

@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!

spencergibb

spencergibb commented on Aug 18, 2017

@spencergibb
Member

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

SeauWong

SeauWong commented on Aug 18, 2017

@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();
}
charlesvhe

charlesvhe commented on Mar 7, 2018

@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);
                }
            }
        }
    }
}
rmfish

rmfish commented on Jan 10, 2019

@rmfish

Create a custom SpringMvcContract bean which override processAnnotationsOnParameter method:

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

ryanjbaxter

ryanjbaxter commented on Jan 14, 2019

@ryanjbaxter
Contributor

I dont think this is still an issue.

eefnrowe

eefnrowe commented on Jan 15, 2019

@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);
}
LUCKYZHOUSTAR

LUCKYZHOUSTAR commented on Jan 30, 2019

@LUCKYZHOUSTAR

@eefnrowe thanks

maketubo

maketubo commented on Feb 19, 2020

@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没问题?

cbjjensen

cbjjensen commented on Aug 31, 2020

@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());
        }
king555317

king555317 commented on Apr 11, 2025

@king555317

切换httpclient即可解决

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @codefromthecrypt@ryanjbaxter@spencergibb@charlesvhe@rmfish

        Issue actions

          Suggest supporting POJO as request parameter · Issue #1253 · spring-cloud/spring-cloud-netflix