SpringBoot系列教程之定義接口返回類型的幾種方式
SpringBoot 系列教程之定義接口返回類型的幾種方式
實現一個 web 接口返回 json 數據,基本上是每一個 javaer 非常熟悉的事情了;那麼問題來了,如果我有一個接口,除了希望返回 json 格式的數據之外,若也希望可以返回 xml 格式數據可行麼?
答案當然是可行的,接下來我們將介紹一下,一個接口的返回數據類型,可以怎麼處理
I. 項目搭建
本文創建的實例工程採用 SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ idea
進行開發
1. pom 依賴
具體的 SpringBoot 項目工程創建就不贅述了,對於 pom 文件中,需要重點關注下面兩個依賴類
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> </dependencies>
注意 jackson-datafromat-xml
這個依賴,加上這個主要時為了支持返回 xml 格式的數據
II. 返回類型設置的多種方式
正常來講,一個 RestController 的接口,默認返回的是 Json 格式數據,當我們引入了上面的 xml 包之後,會怎樣呢?返回的還是 json 麼?
1.通過 produce 設置返回類型
如果一個接口希望返回 json 或者 xml 格式的數據,最容易想到的方式就是直接設置 RequestMapping
註解中的 produce 屬性
這個值主要就是用來設置這個接口響應頭中的 content-type
; 如我們現在有兩個接口,一個指定返回 json 格式數據,一個指定返回 xml 格式數據,可以如下寫
@RestController public class IndexRest{ @Data public static class ResVo<T> { private int code; private String msg; private T data; public ResVo(int{ this.code = code; this.msg = msg; this.data = data; } } @GetMapping(path = "/xml", produces = {MediaType.APPLICATION_XML_VALUE}) public ResVo<String> xml(){ return new ResVo<>(0, "ok", "返回xml"); } @GetMapping(path = "/json", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResVo<String> json(){ return new ResVo<>(0, "ok", "返回json"); } }
上面的實現中
produces = application/xml produces = applicatin/json
接下來我們訪問一下看看返回的是否和預期一致
從上面截圖也可以看出,xml 接口返回的是 xml 格式數據;json 接口返回的是 json 格式數據
2. 通過請求頭 accept 設置返回類型
上面的方式,非常直觀,自然我們就會有一個疑問,當接口上不指定 produces 屬性時,直接訪問會怎麼表現呢?
@GetMapping(path = "/") public ResVo<String> index(){ return new ResVo<>(0, "ok", "簡單的測試"); }
請注意上面的截圖,兩種訪問方式返回的數據類型不一致
application/xhtml+xml
那麼問題來了,為什麼兩者的表現形式不一致呢?
對着上面的圖再看三秒,會發現主要的差別點就在於請求頭 Accept
不同;我們可以通過這個請求頭參數,來要求服務端返回我希望的數據類型
如指定返回 json 格式數據
curl 'http://127.0.0.1:8080' -H 'Accept:application/xml' -iv curl 'http://127.0.0.1:8080' -H 'Accept:application/json'
從上面的執行結果也可以看出,返回的類型與預期的一致;
説明
請求頭可以設置多種 MediaType,用英文逗號分割,後端接口會根據自己定義的 produce 與請求頭希望的 mediaType 取交集,至於最終選擇的順序則以 accept 中出現的順序為準
看一下實際的表現來驗證下上面的説法
通過請求頭來控制返回數據類型的方式可以説是非常經典的策略了,(遵循 html 協議還有什麼好説的呢!)
3. 請求參數來控制返回類型
除了上面介紹的兩種方式之外,還可以考慮為所有的接口,增加一個根據特定的請求參數來控制返回的類型的方式
比如我們現在定義,所有的接口可以選傳一個參數 mediaType
,如果值為 xml,則返回 xml 格式數據;如果值為 json,則返回 json 格式數據
當不傳時,默認返回 json 格式數據
基於此,我們主要藉助 mvc 配置中的內容協商 ContentNegotiationConfigurer
來實現
@SpringBootApplication public class Application implements WebMvcConfigurer{ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer){ configurer.favorParameter(true) // 禁用accept協商方式,即不關心前端傳的accept值 // .ignoreAcceptHeader(true) // 哪個放在前面,哪個的優先級就高; 當上面這個accept未禁用時,若請求傳的accept不能覆蓋下面兩種,則會出現406錯誤 .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) // 根據傳參mediaType來決定返回樣式 .parameterName("mediaType") // 當acceptHeader未禁用時,accept的值與mediaType傳參的值不一致時,以mediaType的傳值為準 // mediaType值可以不傳,為空也行,但是不能是json/xml之外的其他值 .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML); } public static void main(String[] args){ SpringApplication.run(Application.class); } }
上面的實現中,添加了很多註釋,先別急;我來逐一進行説明
.parameterName("mediaType") // 當acceptHeader未禁用時,accept的值與mediaType傳參的值不一致時,以mediaType的傳值為準 // mediaType值可以不傳,為空也行,但是不能是json/xml之外的其他值 .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML); 複製代碼</pre> 上面這三行代碼,主要就是説,現在可以根據傳參 mediaType 來控制返回的類型,我們新增一個接口來驗證一下 <pre class="prettyprint hljs dart">@GetMapping(path = "param") public ResVo<String> params(@RequestParam(name = "mediaType", required = false) String mediaType) { return new ResVo<>(0, "ok", String.format("基於傳參來決定返回類型:%s", mediaType)); }
我們來看下幾個不同的傳參表現
# 返回json格式數據 curl 'http://127.0.0.1:8080/param?mediaType=json' -iv # 返回xml格式數據 curl 'http://127.0.0.1:8080/param?mediaType=xml' -iv # 406錯誤 curl 'http://127.0.0.1:8080/param?mediaType=text' -iv # 走默認的返回類型,json在前,所以返回json格式數據(如果將xml調整到前面,則返回xml格式數據,主要取決於 `.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)`) curl 'http://127.0.0.1:8080/param'
疑問:若請求頭中傳遞了 Accept 或者接口上定義了 produce,會怎樣?
當指定了 accept 時,並且傳參中指定了 mediaType,則以傳參為準
- 如
accept: application/json,application.xml
, 此時mediaType=json
, 返回 json 格式 - 如
accept: application/json
, 此時mediaTyep=xml
, 返回 xml 格式 - 如
accept: text/html
,此時mediaType=xml
,此時返回的也是 xml 格式 - 如
accept: text/html
,此時mediaType
不傳時 ,因為無法處理text/html
類型,所以會出現 406 - 如
accept: application/xml
, 但是mediaType
不傳,雖然默認優先是 json,此時返回的也是 xml 格式,與請求頭希望的保持一致
但是若傳參與 produce 衝突了,那麼就直接 406 異常,不會選擇 mediaType 設置的類型
- 如
produce = applicatin/json
, 但是mediaType=xml
,此時就會喜提 406
細心的小夥伴可能發現了上面的配置中,註釋了一行 .ignoreAcceptHeader(true)
,當我們把它打開之後,前面説的 Accept 請求頭可以隨意傳,我們完全不 care,當做沒有傳這個參數進行處理即可開
4.小結
本文介紹了三種方式,控制接口返回數據類型
方式一
接口上定義 produce, 如 @GetMapping(path = "p2", produces = {"application/xml", "application/json"})
注意 produces 屬性值是有序的,即先定義的優先級更高;當一個請求可以同時接受 xml/json 格式數據時,上面這個定義會確保這個接口現有返回 xml 格式數據
方式二
藉助標準的請求頭 accept,控制希望返回的數據類型;但是需要注意的時,使用這種方式時,要求後端不能設置 ContentNegotiationConfigurer.ignoreAcceptHeader(true)
在實際使用這種方式的時候,客户端需要額外注意,Accept 請求頭中定義的 MediaType 的順序,是優於後端定義的 produces 順序的,因此用户需要將自己實際希望接受的數據類型放在前面,或者乾脆就只設置一個
方式三
藉助 ContentNegotiationConfigurer
實現通過請求參數來決定返回類型,常見的配置方式形如
configurer.favorParameter(true) // 禁用accept協商方式,即不關心前端傳的accept值 // .ignoreAcceptHeader(true) // 哪個放在前面,哪個的優先級就高; 當上面這個accept未禁用時,若請求傳的accept不能覆蓋下面兩種,則會出現406錯誤 .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) // 根據傳參mediaType來決定返回樣式 .parameterName("mediaType") // 當acceptHeader未禁用時,accept的值與mediaType傳參的值不一致時,以mediaType的傳值為準 // mediaType值可以不傳,為空也行,但是不能是json/xml之外的其他值 .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML);
即添加這個設置之後,最終的表現為:
produce
注意注意:當配置中忽略了 AcceptHeader 時, .ignoreAcceptHeader(true)
,上面第三條作廢
- 設計模式之狀態模式
- 如何實現數據庫讀一致性
- 我是怎麼入行做風控的
- C 11精要:部分語言特性
- 吳恩達來信:人工智能領域的求職小 tips
- EasyCV帶你復現更好更快的自監督算法-FastConvMAE
- 某車聯網App 通訊協議加密分析(四) Trace Code
- 帶你瞭解CANN的目標檢測與識別一站式方案
- EasyNLP玩轉文本摘要(新聞標題)生成
- PostgreSQL邏輯複製解密
- 基於 CoreDNS 和 K8s 構建雲原生場景下的企業級 DNS
- 循環神經網絡(RNN)可是在語音識別、自然語言處理等其他領域中引起了變革!
- 技術分享| 分佈式系統中服務註冊發現組件的原理及比較
- 利用谷歌地圖採集外貿客户的電話和手機號碼
- 跟我學Python圖像處理丨關於圖像金字塔的圖像向下取樣和向上取樣
- 帶你掌握如何使用CANN 算子ST測試工具msopst
- 一招教你如何高效批量導入與更新數據
- 一步步搞懂MySQL元數據鎖(MDL)
- 你知道如何用 PHP 實現多進程嗎?
- KubeSphere 網關的設計與實現(解讀)