相信很多同學都了解過(或者面試前都會復(fù)習過)springMVC的執(zhí)行流程,如下圖:

轉(zhuǎn)載請注明出處:Michael孟良
這里我想細節(jié)地理解下springMVC報400(也就是上圖第5步拿到HandlerAdapter后,前往handler時出的錯)的執(zhí)行流程,希望對讀者之后的工作或面試有幫助。
我們舉個簡單的例子(因時間關(guān)系, 我用springboot的2.1.3.RELEASE版本作為這個springMVC的實驗載體):

這里什么都不傳,就傳一個userId并指明是一個int類型

在postman將userId賦值為6e,然后看他的報錯流程。
第一步常規(guī)操作,用戶請求首先去到SpringMVC的DispatcherServlet類里面的doDispatch方法

用戶現(xiàn)在拿到了HandlerAdapter,正準備去拿Resolver解析器。這時會跳到HandlerMethodArgumentResolverComposite類的resolveArgument方法里面

這里我補充一下,參數(shù)前的注解如:@RequestPart @RequestParam @RequestBody @ModelAttribute ... 每個注解有不同的resolver解析器,例如@RequestPart的參數(shù), 就會調(diào)用RequestPartMethodArgumentResolver這個class去解析參數(shù),如果參數(shù)前面沒有注解,這時springMVC 會默認為@RequestParam,并且跳到RequestParamMethodArgumentResolver這個解析器。

這里會看到首先判斷請求進來的是不是MultipartFile , 如果不是就會直接以String格式拿到參數(shù)。 所以為什么如果要上傳文件, controller的file一定要選MultipartFile,不然就會以String形式送到下面的邏輯代碼,最后match不到而報錯。
ok, 拿到用戶錯誤的‘6e’的userId后,請求就會跳到AbstractNamedValueMethodArgumentResolver的resolveArgument方法:

這里到最關(guān)鍵的一步了:
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
這里arg是‘6e’ (不是int類型的參數(shù)), parameter.getParameterType()為int,左跳右跳,跳到GenericConversionService的covert

拿到StringToNumberConverter的覆蓋器,最后在StringToNumberConverterFactory這個工廠類里:

用NumberUtils去parseNumber這個不是int的‘6e’,最后爆400:
{
"timestamp": "2019-05-05T04:26:05.337+0000",
"status": 400,
"error": "Bad Request",
"message": "Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: \"6e\"",
"path": "/hello"
}
這里總結(jié)一下:

至此,我們都大概了解了springMVC的報錯流程。這時我們玩深入點:

我們依然用回@RequestParam,這次我們傳實體UserEntity

UserEntity里面再包一個PetEntity的實體,準備好了json的string:
{ "id": 0, "petEntity": { "id": 19, "name": "string" , "sex": 6 }, "name": "doggie" }

然后我們讀它的流程。
它的流程大致上和上面string轉(zhuǎn)int例子一樣,但去到拿ConverterFactory時,而繼續(xù)往下走:

當?shù)搅薚ypeConverterDelegate的大概148行左右

但找不到對應(yīng)的editor和轉(zhuǎn)換策略時, 就會:
throw new IllegalStateException(msg.toString());
然后不斷往外拋,直至拋到用戶手上:
{
"timestamp": "2019-05-05T05:40:20.581+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Failed to convert value of type 'java.lang.String' to required type 'com.example.demo.UserEentity'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.demo.UserEentity': no matching editors or conversion strategy found",
"path": "/hello"
}

這里其實我覺得應(yīng)該是一個400的bad request . 但springMVC定義成了500。
到這里我有疑惑了,那像其他@RequestBdoy @RequestPart ..是怎么將String轉(zhuǎn)實體的呢?帶著疑惑我又做了個實驗:


這次用@RequestPart 做實驗,發(fā)現(xiàn)在去到RequestPartMethodArgumentResolver時


會跳到AbstractMessageConverterMethodArgumentResolver,這個class里面有個messageConverters的list:
List<HttpMessageConverter<?>> messageConverters;
這里就包含12個converter, 最后一個就是FastJsonHttpMessageConverter , 經(jīng)過兩層if if 過濾后得到當前converter為FastJsonHttpMessageConverter ,當?shù)搅?/p>
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
springMVC 就會將參數(shù)交給第三方插件,也就是將FastJsonHttpMessageConverter 賦給了genericConverter,進行后續(xù)操作:

到這里可以看出,最后會交由阿里插件fastjson去完成String 轉(zhuǎn) 實體對象。