SpringBoot自定義內(nèi)容協(xié)商的實(shí)現(xiàn)
現(xiàn)象演示
假設(shè)有一個(gè)需求是根據(jù)終端的不同,返回不同形式的數(shù)據(jù),比如 PC 端需要以 HTML 格式返回?cái)?shù)據(jù),APP、小程序端需要以 JSON 格式返回?cái)?shù)據(jù)。這時(shí)我們是 coding 幾個(gè)相似的接口?還是在一個(gè)接口里面做復(fù)雜判斷處理??jī)蓚€(gè)方案貌似都不理想,一旦需求改動(dòng),維護(hù)的東西就比較多,這時(shí)候我們利用 SpringBoot 的內(nèi)容協(xié)商功能,就可以很好的簡(jiǎn)化邏輯,案例演示如下:
創(chuàng)建實(shí)體類Dog
public class Dog { private String name; public Dog() { } public Dog(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; } }
創(chuàng)建Controller
@RestController @RequestMapping("/content_negotiation") public class ContentNegotiationController { @GetMapping("/simple") public Dog getDog() { return new Dog("wangcai"); } }
開(kāi)啟參數(shù)形式內(nèi)容協(xié)商
spring: mvc: contentnegotiation: favor-parameter: true
添加POM依賴 (讓 SpringBoot 有返回相關(guān)格式數(shù)據(jù)的能力)
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.16.1</version> </dependency>
請(qǐng)求及響應(yīng)
返回 JSON 格式的數(shù)據(jù)
返回 HTML 格式的數(shù)據(jù)
源碼解析
HandlerMethodReturnValueHandlerComposite#handleReturnValue
總體分為兩步:
- 選擇一個(gè) HandlerMethodReturnValueHandler 來(lái)處理當(dāng)前返回值
- 處理返回值
選擇 HandlerMethodReturnValueHandler
SpringBoot 會(huì)手動(dòng)注冊(cè)的一些 HandlerMethodReturnValueHandler ,一共有15 個(gè) (SpringBoot 版本 2.6.13),我們主要關(guān)注 RequestResponseBodyMethodProcessor 這個(gè)handler。
RequestResponseBodyMethodProcessor#supportsReturnType
如果接口方法含有 @ResponseBody 注解,或者相關(guān)Controller上含有 @ResponseBody 注解,則RequestResponseBodyMethodProcessor 都可以處理
處理返回值
writeWithMessageConverters 方法大概有以下幾個(gè)步驟:
- 獲取 acceptableTypes
- 獲取 producibleTypes
- 獲取 mediaTypesToUse
- 給 mediaTypesToUse 排序
- 獲取 selectedMediaType
- 寫(xiě)出數(shù)據(jù)
獲取 acceptableTypes
分為兩個(gè)分支:
- Response 是否指定 Content-Type,并且 Content-Type 的類型不是 */*,則直接跳轉(zhuǎn)到步驟6 (寫(xiě)出數(shù)據(jù))
- 獲取 acceptableTypes
AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
默認(rèn)情況下,只有 HeaderContentNegotiationStrategy ,因?yàn)槲覀冊(cè)诂F(xiàn)象演示的時(shí)候,將屬性 spring.mvc.contentnegotiation.favor-parameter 設(shè)置為 true,所以多出來(lái)一個(gè) ParameterContentNegotiationStrategy 。如果 ParameterContentNegotiationStrategy 的 resolveMediaTypes 方法的返回值不為 null 且不為 MEDIA_TYPE_ALL_LIST,則以 ParameterContentNegotiationStrategy 的 resolveMediaTypes 方法返回值為準(zhǔn),即 ParameterContentNegotiationStrategy 的優(yōu)先級(jí)高于 HeaderContentNegotiationStrategy
ParameterContentNegotiationStrategy#resolveMediaTypes
getMediaTypeKey
默認(rèn)情況下,parameterName 的值為 format
修改 parameterName 默認(rèn)值
spring: mvc: contentnegotiation: favor-parameter: true parameter-name: custom_format
resolveMediaTypeKey
就是以 parameterName 對(duì)應(yīng)的屬性值為 key, 從 URL 中獲取 value,再以該 value 為 mediaTypeKey 從一個(gè) map (mediaTypes)中獲取 MediaType
mediaTypes的初始賦值
WebMvcConfigurationSupport#mvcContentNegotiationManager
因?yàn)槲覀冊(cè)诂F(xiàn)象演示的時(shí)候添加了相關(guān)POM依賴,所有 mediaTypes 的 內(nèi)容如下所示:
即默認(rèn)情況下,format 的參數(shù)值含義如下 :
- json:acceptableTypes 為 [ application/json ]
- xml:acceptableTypes 為 [ application/xml ]
- 其他:acceptableTypes 為 [ */*]
也可以自定義key,配置如下所示,這時(shí)候如果參數(shù)攜帶 custom_format=lanyu,系統(tǒng)也會(huì)返回 application/xml 格式的數(shù)據(jù)
spring: mvc: contentnegotiation: favor-parameter: true parameter-name: custom_format media-types: {lanyu : application/xml}
HeaderContentNegotiationStrategy#resolveMediaTypes
HeaderContentNegotiationStrategy 的 resolveMediaTypes 方法比較簡(jiǎn)單,就是獲取請(qǐng)求頭中 Accept 的值
獲取 producibleTypes
大概分為以下幾種情況
Request 域的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 是否為 null
屬性值不為 null:返回指定的 mediaTypes
屬性值為 null
- 是否存在 messageConverters 的 canWrite 方法返回 true
- 存在:相關(guān) messageConverters 的 getSupportedMediaTypes 方法返回值的集合
- 不存在:MediaType.ALL
獲取 mediaTypesToUse
主要通過(guò) isCompatibleWith 方法判斷 acceptableType 和 producibleType 是不是兼容的,主要有以下幾種情況 :
producibleType 為 null:返回false
producibleType 不為null
- acceptableType 為 */* 或 producibleType為 */* :返回true
- acceptableType 和 producibleType 都不為 */*
- acceptableType 和 producibleType 的 type 一致
- acceptableType 和 producibleType 的 subtype 一致 : 返回true
- acceptableType 和 producibleType 的 subtype 不一致
- acceptableType 或 producibleType 的 subtype 的 isWildcardSubtype 方法返回true
- acceptableType 或 producibleType 的 subtype 為 *:返回true
- acceptableType 的 subtype 以 *+ 開(kāi)頭,并且 acceptableType 的后綴與producibleType一致:返回true
- producibleType 的 subtype 以 *+ 開(kāi)頭,并且 producibleType 的后綴與acceptableType 一致:返回true
- 其他情況:返回false
- acceptableType 與 producibleType 的 subtype 的 isWildcardSubtype 方法都返回false:返回false
- acceptableType 或 producibleType 的 subtype 的 isWildcardSubtype 方法返回true
- acceptableType 和 producibleType 的 type 不一致:返回false
- acceptableType 和 producibleType 的 type 一致
給 mediaTypesToUse 排序
排序規(guī)則1:
- 權(quán)重越大優(yōu)先級(jí)越高
- 參數(shù)個(gè)數(shù)越多優(yōu)先級(jí)越高
如果排序規(guī)則1未判斷出誰(shuí)的優(yōu)先級(jí)高,則使用排序規(guī)則2,排序規(guī)則2如下:
- 權(quán)重越大優(yōu)先級(jí)越高
- type類型不為 *
- subtype類型不為 * 或以 *+ 開(kāi)頭
- 參數(shù)個(gè)數(shù)越多優(yōu)先級(jí)越高
獲取 selectedMediaType
遍歷上一步經(jīng)過(guò)排序的 mediaTypes,如果存在一個(gè) mediaType 滿足以下條件,則直接返回
- type 類型不為 * ,subtype 類型不為 * 且不以 *+ 開(kāi)頭
MediaType 為 */* 或 application/*
寫(xiě)出數(shù)據(jù)
如果存在一個(gè) HttpMessageConverter 的 canWrite 方法返回 true,則使用 HttpMessageConverter 的 write 方法寫(xiě)出數(shù)據(jù)
擴(kuò)展:自定義HttpMessageConverter處理自定義協(xié)議
創(chuàng)建實(shí)體類Cat
public class Cat { private String name; public Cat() { } public Cat(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Cat{" + "name='" + name + '\'' + '}'; } }
創(chuàng)建自定義HttpMessageConverter
public class LanyuHttpMessageConverter implements HttpMessageConverter { @Override public boolean canRead(Class clazz, MediaType mediaType) { return false; } @Override public boolean canWrite(Class clazz, MediaType mediaType) { if (Cat.class == clazz) { return true; } return false; } @Override public List<MediaType> getSupportedMediaTypes() { return Collections.singletonList(MediaType.parseMediaType("lanyu/custom")); } @Override public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { try { Cat cat = (Cat) o; String data = "cat = {name : " + cat.getName() + "}"; OutputStream outputStream = outputMessage.getBody(); outputStream.write(data.getBytes()); outputStream.flush(); } catch (Exception e) { throw new RuntimeException(e); } } }
創(chuàng)建配置類
@Configuration public class MessageConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new LanyuHttpMessageConverter()); } }
接口及響應(yīng)
@GetMapping("/custom_protocol") public Cat getCustomProtocolData() { return new Cat("tom"); }
我們也可以讓我們自定義的協(xié)議支持 URL 傳參形式,配置如下
spring: mvc: contentnegotiation: favor-parameter: true parameter-name: custom_format media-types: {lanyu : lanyu/custom}
到此這篇關(guān)于SpringBoot自定義內(nèi)容協(xié)商的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot 內(nèi)容協(xié)商內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java開(kāi)發(fā)中使用IDEA活動(dòng)模板快速增加注釋的方法
這篇文章主要介紹了java開(kāi)發(fā)中使用IDEA活動(dòng)模板快速增加注釋,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12JAVA浮點(diǎn)數(shù)計(jì)算精度損失底層原理與解決方案
本文主要介紹了JAVA浮點(diǎn)數(shù)計(jì)算精度損失底層原理與解決方案。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹,本文講解了為何使用工廠模式、工廠方法、抽象工廠、Java工廠模式舉例等內(nèi)容,需要的朋友可以參考下2015-03-03使用@Service注解出現(xiàn)No bean named 'xxxx'&
這篇文章主要介紹了使用@Service注解出現(xiàn)No bean named 'xxxx' available]錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Java代理的幾種實(shí)現(xiàn)方式總結(jié)
本文將通過(guò)例子說(shuō)明java代理的幾種實(shí)現(xiàn)方式,并比較它們之間的差異,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12java 垃圾回收機(jī)制以及經(jīng)典垃圾回收器詳解
這篇文章主要介紹了java 垃圾回收機(jī)制以及經(jīng)典垃圾回收器詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07