Spring原始碼學習之十一:SpringMVC-@RequestBody接收json資料包415

wind瑞_發表於2017-10-19

網路上對這個問題的分析及解決不是很深入,大部分並不能解決問題,而且內容基本相同,拿來主義,把內容放在自己的部落格上!

報錯原因可能有兩種情況:
1.請求頭中沒有設定Content-Type引數,或Content-Type引數值不是application/json;
2.請求頭中正確設定了Content-Type引數及引數值,但是在專案jar依賴中(pom.xml或build.gradle)沒有新增處理json字串的處理類,如果SpringMVC框架在啟動的時候,檢查com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator有一個不存在或不能載入,則不會註冊MappingJackson2HttpMessageConverter,這個類使用Jackson將json請求引數轉成相應的方法引數;同樣檢查com.google.gson.Gson,如果不存在或不能載入,則不會註冊GsonHttpMessageConverter,這個類使用Gson將json請求引數轉成相應的方法引數;如果依賴的Jackson和Gson都沒有被新增或不能載入,則SpringMVC將找不到對應的引數處理類。

原始碼分析

在使用SpringMVC的時候,都會新增

註解,這個註解下有很多可以配置的擴充套件引數,有興趣的可以研究一下。有這個註解,就必定有對應的註解解析,檢視NamespaceHandler介面的實現類,發現有一個MvcNamespaceHandler。
annotation-driven註解做了什麼,直接看AnnotationDrivenBeanDefinitionParser類。這個類中主要的就是parse方法,這個方法中做了很多重要的事,如對一些可擴充套件的引數進行了解析註冊,這些不是本篇的重點,有興趣的可以研究一下,關注重點程式碼。
程式碼中的messageConverters是訊息轉換器集合,裡面包含了對json、xml、atom、rss格式報文的轉換。接著,把messageConverters新增到RequestMappingHandlerAdapter中,RequestMappingHandlerAdapter是處理@RequestMapping註解的HandlerAdapter,簡單說就是標註了@RequestMapping註解的Controller,是經過RequestMappingHandlerAdapter進行呼叫的。messageConverters是它的一個屬性,程式碼如下。
繼續看AnnotationDrivenBeanDefinitionParser類,分析上圖紅框中的ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext),深入這個getMessageConverters方法。
romePresent、jaxb2Present、jackson2Present、jackson2XmlPresent、gsonPresent為true則將對應的轉換器包裝成BeanDefinition,然後將其新增到messageConverters集合中。這幾個布林變數的值在AnnotationDrivenBeanDefinitionParser類的開頭處就賦值了。
如果相應的實現類存在並且可以被載入,則對應的布林變數值為true,否則為false。也就是說,如果SpringMVC框架在啟動的時候,檢查com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator有一個不存在或不能載入,則不會註冊MappingJackson2HttpMessageConverter,這個類使用Jackson將json請求引數轉成相應的方法引數;同樣檢查com.google.gson.Gson,如果不存在或不能載入,則不會註冊GsonHttpMessageConverter,這個類使用Gson將json請求引數轉成相應的方法引數;如果依賴的Jackson和Gson都沒有被新增或不能載入,則SpringMVC將找不到json引數轉換類,也就沒辦法處理。

如果配置了json引數轉換處理類,SpringMVC框架將根據請求頭中的Content-Type引數遍歷messageConverters,選擇匹配的轉換器類,進行引數轉換。如果Content-Type引數值型別是messageConverters中不支援的,那麼就沒辦法做轉換。

總結

首先,SpringMVC框架在啟動的時候會遍歷Spring容器中的所有bean,對標註了@Controller或@RequestMapping註解的類中方法進行遍歷,將類和方法上的@RequestMapping註解值進行合併,使用@RequestMapping註解的相關引數值(如value、method等)封裝一個RequestMappingInfo,將這個Controller例項、方法及方法引數資訊(型別、註解等)封裝到HandlerMethod中,然後以RequestMappingInfo為key,HandlerMethod為value存到一個以Map為結構的handlerMethods中。

接著,將@RequestMapping註解中的value(即請求路徑)值取出,即url,然後以url為key,以RequestMappingInfo為value,存到一個以Map為結構的urlMap屬性中。

客戶端發起請求的時候,根據請求的URL到urlMap中查詢,找到RequestMappingInfo,然後根據RequestMappingInfo到handlerMethods中查詢,找到對應的HandlerMethod,接著將HandlerMethod封裝到HandlerExecutionChain;接著遍歷容器中所有HandlerAdapter實現類,找到支援這次請求的HandlerAdapter,如RequestMappingHandlerAdapter,然後執行SpringMVC攔截器的前置方法(preHandle方法),然後對請求引數解析及轉換,這裡主要根據HandlerMethod中封裝的引數資訊(方法引數上的註解)來遍歷argumentResolvers(List結構,儲存了HandlerMethodArgumentResolver介面實現類,不同實現類,實現對不同註解引數的解析,如RequestResponseBodyMethodProcessor可以實現對@RequestBody和@ResponseBody引數的解析),找到支援這個註解的HandlerMethodArgumentResolver實現類,然後解析請求引數。

插播一下請求引數的解析及轉換,下圖是HandlerMethodArgumentResolver介面的實現類。
從上圖中可以看到很多常見註解引數的解析類,這裡分析RequestResponseBodyMethodProcessor,其它處理類感興趣的可以自己研究一下。RequestResponseBodyMethodProcessor會從請求頭中獲取Content-Type引數值,例如application/json,然後遍歷messageConverters,查詢能夠處理這種Content-Type的轉換器類,如果messageConverters中有可以處理application/json請求的處理類,如Jackson或Gson,則使用Jackson或Gson對請求體中的引數進行讀取轉換,轉換成具體方法引數型別,下面是Jackson具體的處理程式碼。
如果messageConverters沒有匹配的處理類,那就會報415。

最後,(使用反射)呼叫具體Controller的對應方法返回一個ModelAndView物件,執行攔截器的後置方法(postHandle方法),然後對返回的結果進行處理,最後執行afterCompletion方法。


相關文章