rest api參數與content-type,apicontent-type

來源:互聯網
上載者:User

rest api參數與content-type,apicontent-type

最近為項目組提供rest api 時遇到了關於介面參數的傳遞問題,主要是沒有充分考慮到第三方調用者的使用方式,應該盡量的去相容公司之前提供出去的介面調用方式,這樣可以降低第三方調用者的學習成本,儘管之前的方式並不是那麼的推薦,好的做法是即相容老的做法也支援推薦的做法。

對於基於http post介面,Content-type我會優先選擇application/json,但公司之前提供的介面恰恰採用了application/x-www-form-urlencoded,它是表單預設的提交類型,基於key/value形式提交到服務端的。spring mvc是如何接收下面兩種經典資料的? (至於form-data,它即可以傳索引值對也可以上傳檔案,這裡不涉及到檔案所以只討論下面兩種):

  • Content-type=application/json:需要在參數上增加@RequestBody這個註解,說明參數是從http的requestbody中擷取。

    

        中的參數,是標準的json格式,對前端js非常友好。

       

  • Content-type=application/x-www-form-urlencoded,參數上不能增加@RequestBody的註解

         的可以看出參數形式與get請求時,URL後面的參數格式

       


為什麼不推薦採用application/x-www-form-urlencoded這種類型,它有如下問題:

  • 測試困難,就別想通過postman這類工具測試:提交到服務端實際上是一個MultiValueMap,如果value中的對象也是一個對象,那麼在構建這個參數時就非常困難,看下它的過程
    •  採用key1=value1&key2=value2這種形式將所有參數拼接起來,從一長串字元中想瞭解每個參數的含義沒有個好眼力怕是不行。
    •  value要進行編碼,編碼之後的對調試者不友好。
    •  value是複雜物件的情況更加糟糕,一般只能通過程式來序列化得到參數,想手寫基本不可能。
  • 用戶端調用複雜

        需要去構建List<NameValuePair>,一般頁面傳遞的參數都是一個實體物件Model,需要額外的將這個Model轉換成List<NameValuePair>,如果這個對象複雜,那麼構建這個Key/Value就夠人煩的了。這裡給一個java通過apache httpclient調用的對比,看看哪一個簡單。

    • application/x-www-form-urlencoded

                需要手工將model轉換成NameValuePair。

          

    • application/json

                 這裡只需要Model即可,不需要二次轉換,結構也非常清楚。

            

  • key/value的語言表達形式沒有json強,下面兩種你更加喜歡哪一個呢?
    • 字串

             

              post man這類模似http請求的工具中,如果key對應的value是個對象,那麼你需要通過工具得到它的序列化之後的字串然後填寫到欄位中,想想都煩。如果你說我不需要通過這些模似工具測試,那就另當別論

            

    • json

              

  • 資料結構更加複雜

   如果需要提交的對象非常複雜,屬性非常多,如果將所有的屬性都構建到MultiValueMap中,那個Map的構建會非常複雜,試想如果對象有多級嵌套對象呢。所有為了避免這個問題,我們將需要提交的業務對象做為一個key來儲存,value就是對象序列化之後的字串。再加了一些非業務參數,比如安全方面的token等參數,有效降低了MultiValueMap構建的複雜度。但這種方式相對於json的傳遞方式來講層次更深。如,我們的參數多了一層,jsonParam。

    



如果解決呢?
不能不相容現有的模式,但又想支援json,焦點就是在參數的接收上,讓其能夠完美的相容上述兩種參數傳遞,這裡可以從HttpMessageConverter著手,這個就是用來將請求的參數映射到spring mvc方法中的實體參數的。我們可以編寫一個自訂的類,內部借用FormHttpMessageConverter來接收MultiValueMap,即使方法參數上增加了@RequestBody的註解,也會走我們自訂的converter,就有機會去重新給參數賦值。

這個方法中需要解決一個問題,就是用戶端傳遞時每個參數都是當成字串來處理的,這種導致我們通過FormHtppMessageConverter轉換成Map時,原本是對象的屬性被識別成字串,而不是object,結果就是在還原序列化時會出錯。好在,上面我們將需要提交的對象封裝了一次,產生一個公用的object參數jsonParam,只需要處理這一個特殊對象。做法就是從Map取出jsonParam,然後對其內容進行還原序列化,更新Map值,再次進行還原序列化就正常了。
  
     中的做法目前有如下問題

  • 序列化的欄位是約定好的,也是基於我們的post model基本上來處理的,是針對性的converter
  • 代碼最後面調用的jackon的convertValue,對需要還原序列化的物件類型有要求,好像不支援泛型型別,比如這種類型的就不行: CommonParamInfoDto<SearchParamInfo<ProductSearchInfo>>

     完整的conveter代碼如下,其實主要代碼就是貼圖中的那麼對特定欄位的序列化處理,其它的方法都是預設即可。

public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> {    private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();    private final ObjectMapper objectMapper = new ObjectMapper();    private static final LinkedMultiValueMap<String, ?> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>();    private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS            = (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass();    @Override    public boolean canRead(Class clazz, MediaType mediaType) {        return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);    }    @Override    public boolean canWrite(Class clazz, MediaType mediaType) {        return false;    }    @Override    public List<MediaType> getSupportedMediaTypes() {        return formHttpMessageConverter.getSupportedMediaTypes();    }    @Override    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {        Map input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap();        String jsonParamKey="jsonParam";        if(input.containsKey(jsonParamKey)) {            String jsonParam = input.get(jsonParamKey).toString();            SearchParamInfo<Object> searchParamInfo = new SearchParamInfo<Object>();            Object jsonParamObj = JsonHelper.json2Object(jsonParam, searchParamInfo.getClass());            input.put("jsonParam", jsonParamObj);        }        Object objResult= objectMapper.convertValue(input, clazz);        return objResult;    }    @Override    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException {        throw new UnsupportedOperationException("");    }}

 

     配置,寫好了conveter之後,需要在設定檔中配置上才會生效。

    

 

     最後,我們的方法就可以這樣寫,即可以支援 key/value對,也支援json

    

我的目的在於api的參數即能支援application/x-www-form-urlencoded也能支援application/json,上面是我目前能想到的辦法,如果大家有其它更好的辦法多多指點。

 

     我又可以愉快的使用post man測試了。而且可以推薦第三方調用者優先使用json,我相信這種即能簡化編程又方便調試的優點應該能夠吸引它們。

  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.