Feign 實現 GET 方法傳遞 POJO
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