目錄
一、前言
二、SpringBoot 內容協商介紹
2.1 什么是內容協商
2.2 內容協商機制深入理解
2.2.1 內容協商產生的場景
2.3 內容協商實現的常用方式
2.3.1 前置準備
2.3.2?通過HTTP請求頭
2.3.2.1 操作示例
2.3.3 通過請求參數
三、SpringBoot 消息轉換器介紹
3.1 HttpMessageConvertor介紹
3.1.1 常用的HttpMessageConvertor
3.2 如何確定使用哪個消息轉換器
3.2.1 針對請求時的判斷
3.2.2 針對響應時的判斷
3.3 SpringMVC框架默認的消息轉換器
3.3.1 源碼跟蹤
四、自定義消息轉換器
4.1 自定義yaml消息轉換器
4.1.1 引入如下的依賴
4.1.2 自定義yaml媒體類型
4.1.3 自定義HttpMessageConverter
4.1.4 配置消息轉換器
4.1.5 測試與效果驗證
五、寫在文末
一、前言
在微服務開發中,客戶端與服務端數據格式的協商和轉換是一個經常接觸的場景,不同的業務場景下,對于數據格式的要求也不同,比如有的客戶端需要服務器響應XML格式數據,有的需要響應Json格式數據,這就是HTTP消息內容協商機制的源頭,如何滿足復雜多變的HTTP消息轉換需求呢,本篇將詳細分享如何在SpringBoot框架中完成自定義消息轉換器的定制開發與使用。
二、SpringBoot 內容協商介紹
2.1 什么是內容協商
內容協商(Content Negotiation)是指服務器根據客戶端請求來決定響應的內容類型(MIME 類型)。這使得應用程序可以根據客戶端的需求返回不同格式的數據,如 JSON、XML 或 HTML 等。Spring Boot 通過 HttpMessageConverters 和 @RequestMapping 注解等機制來支持內容協商。
2.2 內容協商機制深入理解
內容協商機制是指服務器根據客戶端的請求來決定返回資源的最佳表現形式
-
白話描述:客戶端需要什么格式的數據,服務端就返回什么格式的數據。
比如:
-
客戶端需要json,就響應json;
-
客戶端需要xml,就響應xml;
-
客戶端需要yaml,就響應yaml;
于是,你可能會有疑問,客戶端接收數據時統一采用一種格式,例如Json不就行了,為什么還有那么多的格式要求呢?因為在實際開發中并不是這樣的,比如在下面的場景:
-
遺留的老的系統中的某些業務,處理數據時仍然使用的是xml格式;
-
對于處理速度有要求的這種系統,明確要求使用json格式的數據;
-
對于安全要求比較高的系統,一般要求使用xml格式的數據;
-
某些業務場景下明確指定了某個類型的數據格式...
基于上面的場景,在當下流行的微服務開發模式下,不同的客戶端可能需要后端返回不同格式的數據,于是,對于后端來說,就需要盡可能的適配和滿足這種多樣化的需求場景。
2.2.1 內容協商產生的場景
內容協商的產生具有一定的背景,下面列舉了產生內容協商的一些因素
-
多客戶端支持
-
瀏覽器用戶可能希望看到 HTML 頁面。
-
移動應用開發者可能更傾向于使用 JSON 數據來解析和展示信息。
-
某些舊系統或特定工具可能依賴于 XML 格式的響應。
-
-
提升用戶體驗
-
不同的客戶端有不同的偏好和要求。允許客戶端指定他們想要的內容類型可以提高交互效率,減少不必要的數據處理步驟,并確保最終呈現給用戶的界面是最優化的。例如,某些設備可能更適合處理壓縮過的二進制格式,而不是文本格式的數據。
-
-
遵照RESTful?原則
-
遵循 REST 架構風格的應用程序通常會根據資源的狀態來確定響應的內容類型,而不是依賴于 URL 的變化。這意味著同一個 URI 可以根據請求的不同部分(如 HTTP 方法、查詢參數或頭部信息)返回不同類型的內容。內容協商是實現這一設計理念的關鍵機制之一。
-
2.3 內容協商實現的常用方式
通常來說,通過HTTP請求頭(比如Accept)獲取請求參數(如Format),來指定客戶端偏好接收的內容類型(JSON或XML等),服務器會根據這些信息選擇合適的格式進行響應。下面介紹2種比較常用的方式。
2.3.1 前置準備
為了后續的操作演示,請提前在工程中導入下面幾個基礎依賴
<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.4</version><relativePath/></parent><dependencies><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-yaml</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency></dependencies></project>
2.3.2?通過HTTP請求頭
SpringBoot框架中,如果開發人員不做任何配置的情況下,優先使用這種方式。
-
服務器會根據客戶端發送請求時提交在請求頭中的信息,比如:”Accept:application/json“或"Accept:text/html"來決定最終響應什么格式數據;
2.3.2.1 操作示例
添加一個接口
@RestController
public class UserController {//localhost:8081/getUser@GetMapping("/getUser")public Object getUser(){return new User("mike",18);}}
正常調用,請求頭不加任何參數默認得到的是json結構
如果在請求頭指定響應的數據格式,如下,在Accept中指定是json
curl -H "Accept: application/json" localhost:8081/getUser
如果此時我們指定返回xml格式的數據,此時發現并不好使
curl -H "Accept: application/xml" localhost:8081/getUser
如果需要支持該怎么辦呢?需要做下面的2步:
1)添加依賴jackson-dataformat-xml
-
可以將Java對象轉為xml格式的數據
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2)為實體類增加注解
在當前的User類上面添加注解 @JacksonXmlRootElement用于轉換為xml
package com.congge.entity;import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@JacksonXmlRootElement
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private int age;}
3)請求測試調用
指定為json
指定為xml
總結:
-
客戶端請求的時候,通過在請求協議的請求頭上面增加一個Accept字段,服務端接收到這個值之后,會根據這個參數值動態返回客戶端要求的格式的數據;
2.3.3 通過請求參數
也可以在請求url中拼接指定的請求參數的方式實現,默認的請求參數名為format,格式如下:
http://xxx?format=json
仍然以上面的接口為例,測試一下這種方式的使用
curl http://localhost:8081/getUser?format=json
效果如下:
但是如果指定format為xml,發現并不生效
原因是springboot中在內容協商的處理上,優先使用Accept這種方式,所以如果你要使用format這種方式,還需在配置文件中增加下面的配置信息;
#使用format的方式完成內容協商,如果沒有配置,默認采用Accept的方式實現
spring:mvc:contentnegotiation:favor-parameter: true#默認就叫format,也可以改為自定義的名稱parameter-name: format
設置完成后再次重啟服務測試,此時可以看到兩種格式的數據都支持
三、SpringBoot 消息轉換器介紹
在上面通過案例操作演示介紹了什么是spring框架的內容協商機制,簡單來說就是,客戶端需要什么樣格式的數據,服務端就響應什么格式數據,事實上真的就那么簡單嗎?這背后框架做了什么呢?是不是有什么組件在這個轉換的過程中起作用了呢?接下來就要詳細介紹springmvc框架中對于內容協商的重要技術組件,即HttpMessageConvertor。
3.1 HttpMessageConvertor介紹
HttpMessageConvertor是一個接口,被翻譯為HTTP消息轉換器,即對HTTP消息進行轉換,什么是HTTP消息呢?HTTP消息本質上就是瀏覽器向服務端發送請求時提交的數據,或者是服務器向瀏覽器響應的數據。而HttpMessageConvertor接口就是負責完成請求/響應時數據格式轉換用的。
-
在springmvc框架中提供了很多種HttpMessageConvertor接口的實現類,不同的HTTP消息轉換器具有不同的轉換效果,使用的場景也有區別,有的是負責將Java對象轉為JSON格式的數據,有的負責將Java對象轉為XML格式的數據。
3.1.1 常用的HttpMessageConvertor
springmvc框架內置了一些常用的消息轉換器,正是這些轉換器完成了諸如上述json或xml格式的數據轉換,下面介紹一些常用的框架內置的消息轉換器:
-
FormHttpMessageConvertor
-
常用于處理提交表單數據時候使用的轉換器;
-
-
MappingJackson2HttpMessageConvertor
-
客戶端或瀏覽器提交JSON格式的數據轉換為JAVA對象主要是由這個轉換器處理,比如經常在POST請求接口上面添加的@RequestBody注解;
-
-
JaxbRootElementHttpMessageConvertor
-
將JAVA對象轉為XML格式的數據通常由這個消息轉換器完成;
-
-
StringHttpMessageConvertor
-
將String類型的的數據直接寫入到響應中由這個轉換器完成;
-
3.2 如何確定使用哪個消息轉換器
有這么多的消息轉換器,那么在具體使用的時候,框架是如何確定使用哪種類型的轉換器的呢?
3.2.1 針對請求時的判斷
請求時通常根據下面的條件來確定使用哪個消息轉換器:
-
請求的Content-Type頭信息
-
SpringMVC會檢查Content-Type頭信息,以確定請求體的數據格式,比如:application/json,application/xml...
-
-
方法參數類型
-
控制器方法中接收請求體的參數類型,比如POST請求中有@RequestBody注解;
-
3.2.2 針對響應時的判斷
響應時通常根據以下條件來確定使用哪個消息轉換器:
-
請求提交時,請求頭上的Accept字段
-
Spring MVC 會檢查客戶端請求的 Accept 字段,以確定客戶端期望的響應格式(例如 application/json、application/xml 等);
-
-
方法返回值的類型
-
控制器方法的返回值類型比如: @ResponseBody
-
@ResponseBody + 控制器方法的返回值是String,則使用StringHttpMessageConverter轉換器。(將字符串直接寫入響應體)
-
@ResponseBody + 控制器方法的返回值是Java對象,則使用MappingJackson2HttpMessageConverter轉換器。(將java對象轉換成json格式的字符串寫入到響應體)
-
-
3.3 SpringMVC框架默認的消息轉換器
SpringMVC框架自身已經內置了一些消息轉換器,可以在啟動的時候debug源碼看到,主要包括下面6個
-
ByteArrayHttpMessageConverter
-
用于將字節數組(byte[])與HTTP消息體之間進行轉換。這通常用于處理二進制數據,如圖片或文件。
-
-
StringHttpMessageConverter
-
用于將字符串(String)與HTTP消息體之間進行轉換。它支持多種字符集編碼,能夠處理純文本內容。
-
-
ResourceHttpMessageConverter
-
用于將Spring的Resource對象與HTTP消息體之間進行轉換。Resource是Spring中表示資源的接口,可以讀取文件等資源。這個轉換器對于下載文件或發送靜態資源有用。
-
-
ResourceRegionHttpMessageConverter
-
用于處理資源的部分內容(即“Range”請求),特別是當客戶端請求大文件的一部分時。這對于實現視頻流媒體等功能很有用。
-
-
AllEncompassingFormHttpMessageConverter
-
用于處理表單,是一個比較全面的form消息轉換器。處理標準的application/x-www-form-urlencoded格式的數據,以及包含文件上傳的multipart/form-data格式的數據。
-
-
MappingJackson2HttpMessageConverter
-
使用Jackson庫來序列化和反序列化JSON數據。可以將Java對象轉換為JSON格式的字符串,反之亦然。
-
3.3.1 源碼跟蹤
入口類:WebMvcAutoConfiguration
-
WebMvcAutoConfiguration內部類EnableWebMvcConfiguration
-
EnableWebMvcConfiguration繼承了DelegatingWebMvcConfiguration
-
DelegatingWebMvcConfiguration繼承了WebMvcConfigurationSupport
DelegatingWebMvcConfiguration
繼續進入到WebMvcConfigurationSupport
在這個類中,提供了一個方法 addDefaultHttpMessageConverters,在這個方法中,會將工程中的所有的消息轉換器加進去。下面通過debug源碼的方式跟進一下過程。
啟動springboot工程后,進入該方法,此時messageConverters這個列表還是空的
從源碼不難看出,方法中會new出幾個內置的轉換器加入到這個集合中
在上面的方法中,注意到會有一個判斷的方法,比如:jackson2XmlPresent,它是如何判斷的呢?其實在當前的類中,在靜態代碼塊中維護了一個全局的布爾變量,工程在加載的時候,通過ClassUtils.isPresent方法,傳入類的全路徑,從而判斷是否滿足條件,滿足,則在addDefaultHttpMessageConverters方法執行時候加入進去。
最后在這個方法執行完成的時候,列表中就添加了一些消息轉換器
通過debug源碼不難看出,在實際開發中,只要引入相關的依賴,讓類路徑存在某個類,則對應的消息轉換器就會被加載。
四、自定義消息轉換器
實際項目開發過程中,來自客戶端的需求場景是很多的,當系統內置的轉換器格式不能滿足要求時,比如需要返回yaml格式的數據,或者其他定制化類型的數據時,此時就可以考慮自定義消息轉換器。下面以yaml這種特殊格式的數據為例進行說明。
4.1 自定義yaml消息轉換器
下面看具體的操作步驟。
4.1.1 引入如下的依賴
任何一個能夠處理yaml格式數據的庫都可以,這里選擇使用jackson的庫,因為它既可以處理json,xml,又可以處理yaml。
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
通過下面這段程序測試一下這個SDK的轉換效果
import com.congge.entity.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;public class JavaYamlTest {public static void main(String[] args) throws JsonProcessingException {// 創建YAML工廠類YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); // 禁止使用文檔頭標記// 創建對象映射器ObjectMapper objectMapper = new ObjectMapper(yamlFactory);// 準備數據User user = new User("user01", 12);// 將數據轉換成YAML格式String res = objectMapper.writeValueAsString(user);System.out.println(res);}}
運行可以看到能夠正常轉換
4.1.2 自定義yaml媒體類型
Springboot 默認支持xml和json兩種媒體類型,如果要支持yaml格式的,需新增一個yaml媒體類型,在springboot的配置文件中進行如下配置:
spring:mvc:contentnegotiation:media-types:yaml: text/yaml
注意:
-
以上types后面的yaml是媒體類型的名字,名字可以自己修改,如果媒體類型起名為xyz,那么發送請求時的路徑應該是這樣的:http://localhost:8081/getUser?format=xyz
4.1.3 自定義HttpMessageConverter
編寫一個類,比如:YamlHttpMessageConverter繼承AbstractHttpMessageConverter,需要繼承AbstractHttpMessageConverter這個類,參考下面的代碼:
package com.congge.config;import com.congge.entity.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;
import java.nio.charset.Charset;public class YamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));/*** 將自定義的消息轉換器 和 配置文件中自定義的媒體類型 text/yaml 進行綁定*/public YamlHttpMessageConverter() {super(new MediaType("text", "yaml", Charset.forName("UTF-8")));}/*** 用于指定消息轉換器支持哪些類型的對象轉換,比如這里指定User對象類型的數據進行轉換* @param clazz* @return*/@Overrideprotected boolean supports(Class<?> clazz) {// 表示User類型的數據支持yaml,其他類型不支持return User.class.isAssignableFrom(clazz);}/*** 處理 @RequestBody(將提交的yaml格式數據轉換為java對象)* @param clazz* @param inputMessage* @return* @throws IOException* @throws HttpMessageNotReadableException*/@Overrideprotected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}/*** 處理 @ResponseBody(將java對象轉換為yaml格式的數據)* @param o* @param outputMessage* @throws IOException* @throws HttpMessageNotWritableException*/@Overrideprotected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {this.objectMapper.writeValue(outputMessage.getBody(), o);// 注意:spring框架會自動關閉輸出流,無需程序員手動釋放。}
}
補充說明:
-
所有的消息轉換器,包括自定義的,都需要實現HttpMessageConverter接口,或者繼承AbstractHttpMessageConverter這個類,重寫里面的核心方法。
4.1.4 配置消息轉換器
重寫WebMvcConfigurer接口的configureMessageConverters方法,將上面的自定義消息加入到全局的轉換器列表中。
package com.congge.config;import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Configuration
public class ConverterWebConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new YamlHttpMessageConverter());}}
4.1.5 測試與效果驗證
啟動工程,通過下面的curl命令再次測試,可以看到通過上面的自定義改造已經能夠輸出yaml格式的數據了
curl -H "Accept: text/yaml" localhost:8081/getUser
針對其他類型格式的轉換器,也可以參照上面的步驟進行編寫即可
五、寫在文末
本文詳細介紹了SpringBoot消息轉換器的知識,并通過案例操作演示了如何進行自定義消息轉換器的定制開發和使用,希望對看到的同學有用哦,本篇到此結束,感謝觀看。