Feign 實現 GET 方法傳遞 POJO

Feign 實現 GET 方法傳遞 POJO

作者:Grey

原文地址:

部落格園:Feign 實現 GET 方法傳遞 POJO

CSDN:Feign 實現 GET 方法傳遞 POJO

需求

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 配置

程式碼說明

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 做如下請求

image

返回成功結果。

完整程式碼見:feign-usage

參考資料

重新定義 Spring Cloud 實戰