Feign 实现 GET 方法传递 POJO
作者:Grey
原文地址:
需求
Spring MVC 支持 GET 方法直接绑定 POJO 的,但是 Feign 目前无法做到,有几种解决方案
方案一:把 POJO 拆散成一个一个单独的属性放在方法参数里。
方案二:把方法参数变成Map传递。
方案三:使用 GET 传递 @RequestBody ,但此方式违反 Restful 规范。
方案四(最佳实践):通过实现 Feign 的 RequestInterceptor 中的 apply 方法来进行统一拦截转换处理 Feign 中的 GET 方法多参数传递的问题。
接下来介绍方案四,即最佳实践。
环境
Java 版本:17
Spring Boot 版本:2.7.5
Spring Cloud 版本:2021.0.5
项目结构和说明
- feign-usage:父项目名称
- register-server : 仅作注册中心,无其他业务方法
- src/
- pom.xml
- provider : 服务端端模块
- src/
- pom.xml
- consumer : 客户端模块
- src/
- pom.xml
- pom.xml:父项目 pom 配置
- register-server : 仅作注册中心,无其他业务方法
代码说明
provider 项目中,定义了一个 Controller ,用于接收用户请求,有如下的一个方法。
@RestController @RequestMapping("/user") public class UserController { @RequestMapping(value = "/add", method = RequestMethod.GET) public String addUser(User user, HttpServletRequest request) { String token = request.getHeader("oauthToken"); return "hello, add user : " + user.getName(); } …… }
基于上述两个服务,客户端 consumer 定义了一个 feign 客户端用于请求服务端的服务
@FeignClient(name = "provider") public interface UserFeignService { @RequestMapping(value = "/user/add", method = RequestMethod.GET) String addUser(User user); …… }
用于 feign 使用 GET 无法直接传递 POJO,所以定义如下一个拦截器,在 apply 方法种处理请求并封装成 POJO 发送给服务端,本实例中,我们要封装的是 User 对象
public class User { private Long id; private String name; private int age; // 省略 Get / Set 方法 }
定义的拦截器代码如下
@Component public class FeignRequestInterceptor implements RequestInterceptor { private final ObjectMapper objectMapper; public FeignRequestInterceptor(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void apply(RequestTemplate template) { if (template.method().equals("GET") && template.body() != null) { try { JsonNode jsonNode = objectMapper.readTree(template.body()); template.body(null, StandardCharsets.UTF_8); 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.computeIfAbsent(path, k -> new ArrayList<>()); 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); } } } } }
测试一下,分别启动 register-server,provider,consumer 三个项目,使用 Postman 做如下请求

返回成功结果。
完整代码见:feign-usage