首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

REST API 參數傳遞與 Content-Type 兼容方案:從表單到 JSON 的無縫遷移

     編輯:Java 綜合教學

問題背景:雙重標準的協作困境

項目對外提供 REST API 時,面臨典型衝突:

  • 推薦標準:Content-Type: application/json,請求體直接攜帶 JSON,結構清晰、巢狀對象自然表達、前端友好、Postman 測試便捷
  • 歷史遺留:Content-Type: application/x-www-form-urlencoded,表單預設編碼,key1=value1&key2=value2 扁平結構,嵌套對象序列化極其痛苦、手工測試幾乎不可能

核心需求:單一 Controller 方法同時兼容兩種 Content-Type,不修改現有調用方,新調用方使用 JSON。

Spring MVC 參數綁定機制差異

Content-TypeController 參數註解底層處理器適用場景
application/json@RequestBodyMappingJackson2HttpMessageConverter複雜對象、巢狀結構、陣列
application/x-www-form-urlencoded無註解 或 @ModelAttributeFormHttpMessageConverter簡單扁平表單、文件上傳

關鍵衝突:加上 @RequestBody 後,表單請求會報 400 Bad Request(Required request body is missing);去掉 @RequestBody,JSON 請求無法綁定到對象。

解決方案:自定義 HttpMessageConverter 統一入口

核心思路:實現 HttpMessageConverter<Object>,內部委託 FormHttpMessageConverter 解析表單為 MultiValueMap,針對特定欄位(如 jsonParam)進行二次反序列化,最後統一由 Jackson 轉換為目標對象。

完整實現代碼

public class UnifiedHttpMessageConverter implements HttpMessageConverter<Object> {
    private final FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    // 單例 MultiValueMap Class 引用,避免重複獲取
    private static final LinkedMultiValueMap<String, String> EMPTY_MAP = new LinkedMultiValueMap<>();
    private static final Class<? extends MultiValueMap<String, ?>> MAP_CLASS = 
        (Class<? extends MultiValueMap<String, ?>>) EMPTY_MAP.getClass();

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        // 支援表單類型且 Jackson 能序列化目標類型
        return formConverter.canRead(MAP_CLASS, mediaType) 
            && objectMapper.canSerialize(clazz);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return false; // 僅處理讀取
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return formConverter.getSupportedMediaTypes(); // 繼承表單支援類型
    }

    @Override
    public Object read(Class<?> clazz, HttpInputMessage inputMessage) 
            throws IOException, HttpMessageNotReadableException {
        
        // 1. 先用表單轉換器解析為單值 Map
        Map<String, Object> inputMap = formConverter
            .read(MAP_CLASS, inputMessage).toSingleValueMap();

        // 2. 關鍵處理:若存在約定欄位 jsonParam,進行二次反序列化
        final String JSON_PARAM_KEY = "jsonParam";
        if (inputMap.containsKey(JSON_PARAM_KEY)) {
            String jsonStr = inputMap.get(JSON_PARAM_KEY).toString();
            // 反序列化為通用 Map 或具體 DTO,這裡示範通用處理
            Object jsonObj = objectMapper.readValue(jsonStr, Map.class);
            inputMap.put(JSON_PARAM_KEY, jsonObj);
        }

        // 3. 統一由 Jackson 將處理後的 Map 轉換為目標對象
        return objectMapper.convertValue(inputMap, clazz);
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) {
        throw new UnsupportedOperationException("僅支援請求讀取");
    }
}

Spring 配置註冊

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="com.config.UnifiedHttpMessageConverter" />
        <!-- 保留其他默認轉換器 -->
    </mvc:message-converters>
</mvc:annotation-driven>

配置示意圖

Controller 統一寫法

@PostMapping("/search")
public Result search(@RequestBody SearchRequest request) {
    // 同時支援:
    // 1. 表單提交:jsonParam={"keyword":"xxx","page":1}&token=abc
    // 2. JSON 提交:{"keyword":"xxx","page":1,"token":"abc"}
    return service.search(request);
}

Controller 代碼

方案優缺點分析

優點缺點/限制
零侵入業務代碼,Controller 保持乾淨強依賴約定欄位名 jsonParam,非通用方案
完美兼容現有表單調用方,零升級成本convertValue 不支援泛型類型(如 CommonParamInfoDto<SearchParamInfo<ProductSearchInfo>>)
新調用方享受 JSON 結構化優勢雙重解析帶來微小效能開銷
Postman 測試即刻支援 JSON 格式錯誤訊息追蹤相對困難

進階優化方向

  1. 泛型支援:引入 TypeReference 或自定義 JavaType 解決 convertValue 泛型擦除問題
  2. 多欄位支援:掃描 Map 中所有值為 JSON 字串的欄位自動反序列化
  3. 內容協商:結合 ContentNegotiationManager 根據 Accept 標頭動態選擇響應格式
  4. 遷移策略:設定截止日期,逐步引導調用方遷移至純 JSON,最終移除兼容層

替代方案對比

方案實現複雜度兼容性推薦指數
自定義 HttpMessageConverter(本文)中完全兼容⭐⭐⭐⭐
兩個 Controller 方法(不同路徑/參數)低需調用方配合⭐⭐
Servlet Filter 預處理轉換 Content-Type中完全兼容⭐⭐⭐
@RequestParam Map + 手工綁定高(業務代碼污染)完全兼容⭐
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved