SpringBoot響應(yīng)處理實(shí)現(xiàn)流程詳解
1、相關(guān)依賴
web項(xiàng)目引入的啟動(dòng)器spring-boot-starter-web中含有
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.7.0</version> <scope>compile</scope> </dependency>
這個(gè)依賴下面又有jackson的相關(guān)依賴,用于json的轉(zhuǎn)換
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> <version>2.13.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.13.3</version> <scope>compile</scope> </dependency>
2、ReturnValueHandlers—返回值處理器
之前我們分析了參數(shù)解析器argumentResolvers的相關(guān)源碼,了解了請(qǐng)求中的參數(shù)是如何找到合適的參數(shù)解析器,并與方法的入?yún)⑦M(jìn)行綁定的,那么問題來了,響應(yīng)值的返回值處理器是如何找到的呢?
要分析源碼,我們還是得進(jìn)入DispatcherServlet這個(gè)類下的doDiapatch()方法
與之前分析參數(shù)解析器一樣的流程,我們跟到了RequestMappingHandlerAdapter這個(gè)類下的invokeHandlerMethod()方法
通過斷點(diǎn)可以看到我們默認(rèn)支持15種返回值解析器
那么他是怎么從這么多返回值解析器中選出自己支持的那一個(gè)呢?
我們可以發(fā)現(xiàn),這些返回值解析器都是實(shí)現(xiàn)了HandlerMethodReturnValueHandler接口
package org.springframework.web.method.support; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodReturnValueHandler { boolean supportsReturnType(MethodParameter returnType); void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
此接口下只有2個(gè)方法,一個(gè)是判斷是否支持此類型返回值,另一個(gè)是處理返回值的方法
是不是和之前的參數(shù)解析器的接口類有異曲同工之妙?
沒錯(cuò),我們正是使用這兩個(gè)方法來尋找適合自己的返回值解析器以及處理我們的返回值
跟著斷點(diǎn)一直向下,我們跟到了HandlerMethodReturnValueHandlerComposite類下的handleReturnValue()方法,在這里,我們就找到了對(duì)應(yīng)的解析器并執(zhí)行了相關(guān)方法
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 獲取自己適合的返回值解析器 HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } else { // 調(diào)用該返回值解析器中的處理返回值的方法 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } } @Nullable private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) { // 判斷是否是異步的返回值 boolean isAsyncValue = this.isAsyncReturnValue(value, returnType); Iterator var4 = this.returnValueHandlers.iterator(); HandlerMethodReturnValueHandler handler; // 使用do-while進(jìn)行遍歷,找出支持當(dāng)前返回值類型的解析器 do { do { if (!var4.hasNext()) { return null; } handler = (HandlerMethodReturnValueHandler)var4.next(); } while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)); } while(!handler.supportsReturnType(returnType)); return handler; }
最終,我們確定使用解析器RequestResponseBodyMethodProcessor可以處理標(biāo)注了@ResponseBody的返回值
3、HttpMessageConvert—消息轉(zhuǎn)換器
除了尋找合適的返回值解析器之外,我們還有一個(gè)問題要思考
為什么我們?cè)诳刂破黝惖姆椒ㄉ霞右粋€(gè)@ResponseBody注解就能將響應(yīng)信息轉(zhuǎn)換成json格式呢?
這個(gè)轉(zhuǎn)換是怎樣實(shí)現(xiàn)的呢?
我們接著上面的代碼繼續(xù)往下debug
之前我們已經(jīng)定位到了使用RequestResponseBodyMethodProcessor來處理@ResponseBody的返回值,所以我們繼續(xù)深入到這個(gè)類下的handleReturnValue()
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); // 創(chuàng)建輸入和輸出信息 ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest); ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest); // 使用消息轉(zhuǎn)換器進(jìn)行寫出操作 this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
然后我們來分析解析器父類AbstractMessageConverterMethodProcessor下的writeWithMessageConverters()方法
首先我們了解一個(gè)概念:什么是內(nèi)容協(xié)商?
瀏覽器默認(rèn)會(huì)以請(qǐng)求頭的方式告訴服務(wù)器他能接受什么樣的內(nèi)容類型,這里的q代表優(yōu)先級(jí)
然后服務(wù)器最終根據(jù)自己自身的能力,決定服務(wù)器能生產(chǎn)出什么樣內(nèi)容類型的數(shù)據(jù)
所以在writeWithMessageConverters()方法中我們獲取到瀏覽器能接受什么內(nèi)容
以及服務(wù)器能產(chǎn)生什么內(nèi)容
然后我們經(jīng)過協(xié)商,決定返回application/json格式的數(shù)據(jù)
重點(diǎn)來了:這里有一系列的消息轉(zhuǎn)換器,我們到底使用哪個(gè)呢?
隨便點(diǎn)進(jìn)一個(gè)消息轉(zhuǎn)換器,我們發(fā)現(xiàn)他都是實(shí)現(xiàn)了HttpMessageConverter這個(gè)接口
這里面有2個(gè)方法,canRead()和canWrite()來幫助我們判斷能否支持源類型和目標(biāo)類型的消息讀寫
最終我們確定了,使用MappingJackson2HttpMessageConverter這個(gè)消息轉(zhuǎn)換器來進(jìn)行json轉(zhuǎn)換
package org.springframework.http.converter; import java.io.IOException; import java.util.Collections; import java.util.List; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; public interface HttpMessageConverter<T> { // 讀取,將我們當(dāng)前消息轉(zhuǎn)換器支持的對(duì)象以某種格式讀取進(jìn)來,例如將json數(shù)據(jù)讀取成Person對(duì)象 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); // 寫出,我們當(dāng)前消息轉(zhuǎn)換器支持的對(duì)象以某種格式寫出去,例如將Person對(duì)象以json格式寫出去 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); List<MediaType> getSupportedMediaTypes(); default List<MediaType> getSupportedMediaTypes(Class<?> clazz) { return !this.canRead(clazz, (MediaType)null) && !this.canWrite(clazz, (MediaType)null) ? Collections.emptyList() : this.getSupportedMediaTypes(); } T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
然后我們?cè)诖颂幷{(diào)用MappingJackson2HttpMessageConverter父類AbstractGenericHttpMessageConverter中的write()方法
走到AbstractJackson2HttpMessageConverter類下的writeInternal()方法
可以看出,它利用了底層的Jackson的objectMapper進(jìn)行轉(zhuǎn)換
這樣,我們就完成了Person數(shù)據(jù)到j(luò)son格式的轉(zhuǎn)換
大概流程如下
- 判斷當(dāng)前響應(yīng)頭中是否已經(jīng)有確定的媒體類型,即MediaType
- 獲取客戶端(PostMan、瀏覽器)支持接收的內(nèi)容類型(獲取客戶端Accept請(qǐng)求頭字段)
- 遍歷循環(huán)所有當(dāng)前系統(tǒng)的 MessageConverter,看誰支持操作這個(gè)對(duì)象(Person),找到支持操作Person的converter,把該converter支持的媒體類型統(tǒng)計(jì)出來放到集合中,這樣就獲取到服務(wù)端能提供哪些媒體類型
- 進(jìn)行內(nèi)容協(xié)商,找到最佳匹配媒體類型
- 用 支持 將對(duì)象轉(zhuǎn)為 最佳匹配媒體類型 的converter,調(diào)用它進(jìn)行轉(zhuǎn)化
4、開啟瀏覽器參數(shù)方式內(nèi)容協(xié)商功能
如果需要返回xml格式的數(shù)據(jù),那么需要額外導(dǎo)入相關(guān)依賴
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
我們可以看到,瀏覽器是默認(rèn)支持如下返回格式的,一般情況下,我們無法指定自己需要的返回格式
但是我們可以通過修改配置+添加參數(shù)的方式指定我們需要的格式
首先,我們?cè)趛aml文件中,開啟基于瀏覽器參數(shù)方式內(nèi)容協(xié)商功能
spring:
mvc:
contentnegotiation:
favor-parameter: true
一旦此參數(shù)設(shè)置為true,那么我們的內(nèi)容協(xié)商管理器contentNegotiationManager中,除了原有的從請(qǐng)求頭獲取媒體類型的策略之外,還多了一個(gè)從請(qǐng)求參數(shù)中獲取媒體類型的策略,它支持xml和json兩種媒體類型
然后我們使用http://localhost:8080/testPathVariable/001/split/decade?format=xml或者http://localhost:8080/testPathVariable/001/split/decade?format=json指定我們需要的返回格式
到此這篇關(guān)于SpringBoot響應(yīng)處理實(shí)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)SpringBoot響應(yīng)處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 鍵盤輸入一個(gè)數(shù),輸出數(shù)組中指定元素的示例
今天小編就為大家分享一篇java 鍵盤輸入一個(gè)數(shù),輸出數(shù)組中指定元素的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07chatgpt java環(huán)境調(diào)用源碼實(shí)現(xiàn)demo
這篇文章主要介紹了chatgpt java環(huán)境調(diào)用源碼實(shí)現(xiàn)demo,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02淺談兩個(gè)jar包中包含完全相同的包名和類名的加載問題
下面小編就為大家?guī)硪黄獪\談兩個(gè)jar包中包含完全相同的包名和類名的加載問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09springmvc+ajax+formdata上傳圖片代碼實(shí)例
這篇文章主要介紹了springmvc+ajax+formdata上傳圖片代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java實(shí)現(xiàn)畫圖的詳細(xì)步驟(完整代碼)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著Java實(shí)現(xiàn)畫圖的詳細(xì)步驟展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06解析探秘fescar分布式事務(wù)實(shí)現(xiàn)原理
這篇文章主要為大家解析探秘fescar分布式事務(wù)的實(shí)現(xiàn)原理,fescar的txc模型實(shí)現(xiàn)非常有研究的價(jià)值,所以今天我們來好好翻一翻fescar項(xiàng)目的代碼2022-02-02Mybatis實(shí)現(xiàn)一對(duì)一、一對(duì)多關(guān)聯(lián)查詢的方法(示例詳解)
這篇文章主要介紹了Mybatis實(shí)現(xiàn)一對(duì)一、一對(duì)多關(guān)聯(lián)查詢的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04