作者: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
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