相信很多同學(xué)都了解過(guò)(或者面試前都會(huì)復(fù)習(xí)過(guò))springMVC的執(zhí)行流程,如下圖:
轉(zhuǎn)載請(qǐng)注明出處:Michael孟良
這里我想細(xì)節(jié)地理解下springMVC報(bào)400(也就是上圖第5步拿到HandlerAdapter后,前往handler時(shí)出的錯(cuò))的執(zhí)行流程,希望對(duì)讀者之后的工作或面試有幫助腊尚。
我們舉個(gè)簡(jiǎn)單的例子(因時(shí)間關(guān)系, 我用springboot的2.1.3.RELEASE版本作為這個(gè)springMVC的實(shí)驗(yàn)載體):
這里什么都不傳鸳兽,就傳一個(gè)userId并指明是一個(gè)int類(lèi)型
在postman將userId賦值為6e待讳,然后看他的報(bào)錯(cuò)流程。
第一步常規(guī)操作蛮原,用戶(hù)請(qǐng)求首先去到SpringMVC的DispatcherServlet類(lèi)里面的doDispatch方法
用戶(hù)現(xiàn)在拿到了HandlerAdapter卧须,正準(zhǔn)備去拿Resolver解析器。這時(shí)會(huì)跳到HandlerMethodArgumentResolverComposite類(lèi)的resolveArgument方法里面
這里我補(bǔ)充一下儒陨,參數(shù)前的注解如:@RequestPart @RequestParam @RequestBody @ModelAttribute ... 每個(gè)注解有不同的resolver解析器花嘶,例如@RequestPart的參數(shù), 就會(huì)調(diào)用RequestPartMethodArgumentResolver這個(gè)class去解析參數(shù)蹦漠,如果參數(shù)前面沒(méi)有注解椭员,這時(shí)springMVC 會(huì)默認(rèn)為@RequestParam,并且跳到RequestParamMethodArgumentResolver這個(gè)解析器津辩。
這里會(huì)看到首先判斷請(qǐng)求進(jìn)來(lái)的是不是MultipartFile , 如果不是就會(huì)直接以String格式拿到參數(shù)拆撼。 所以為什么如果要上傳文件, controller的file一定要選MultipartFile喘沿,不然就會(huì)以String形式送到下面的邏輯代碼闸度,最后match不到而報(bào)錯(cuò)。
ok蚜印, 拿到用戶(hù)錯(cuò)誤的‘6e’的userId后莺禁,請(qǐng)求就會(huì)跳到AbstractNamedValueMethodArgumentResolver的resolveArgument方法:
這里到最關(guān)鍵的一步了:
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
這里arg是‘6e’ (不是int類(lèi)型的參數(shù)), parameter.getParameterType()為int,左跳右跳窄赋,跳到GenericConversionService的covert
拿到StringToNumberConverter的覆蓋器哟冬,最后在StringToNumberConverterFactory這個(gè)工廠(chǎng)類(lèi)里:
用NumberUtils去parseNumber這個(gè)不是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的報(bào)錯(cuò)流程浩峡。這時(shí)我們玩深入點(diǎn):
我們依然用回@RequestParam,這次我們傳實(shí)體UserEntity
UserEntity里面再包一個(gè)PetEntity的實(shí)體错敢,準(zhǔn)備好了json的string:
{ "id": 0, "petEntity": { "id": 19, "name": "string" , "sex": 6 }, "name": "doggie" }
然后我們讀它的流程翰灾。
它的流程大致上和上面string轉(zhuǎn)int例子一樣,但去到拿ConverterFactory時(shí),而繼續(xù)往下走:
當(dāng)?shù)搅薚ypeConverterDelegate的大概148行左右
但找不到對(duì)應(yīng)的editor和轉(zhuǎn)換策略時(shí)纸淮, 就會(huì):
throw new IllegalStateException(msg.toString());
然后不斷往外拋平斩,直至拋到用戶(hù)手上:
{
"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"
}
這里其實(shí)我覺(jué)得應(yīng)該是一個(gè)400的bad request . 但springMVC定義成了500。
到這里我有疑惑了咽块,那像其他@RequestBdoy @RequestPart ..是怎么將String轉(zhuǎn)實(shí)體的呢绘面?帶著疑惑我又做了個(gè)實(shí)驗(yàn):
這次用@RequestPart 做實(shí)驗(yàn),發(fā)現(xiàn)在去到RequestPartMethodArgumentResolver時(shí)
會(huì)跳到AbstractMessageConverterMethodArgumentResolver侈沪,這個(gè)class里面有個(gè)messageConverters的list:
List<HttpMessageConverter<?>> messageConverters;
這里就包含12個(gè)converter揭璃, 最后一個(gè)就是FastJsonHttpMessageConverter , 經(jīng)過(guò)兩層if if 過(guò)濾后得到當(dāng)前converter為FastJsonHttpMessageConverter ,當(dāng)?shù)搅?/p>
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
springMVC 就會(huì)將參數(shù)交給第三方插件峭竣,也就是將FastJsonHttpMessageConverter 賦給了genericConverter塘辅,進(jìn)行后續(xù)操作:
到這里可以看出,最后會(huì)交由阿里插件fastjson去完成String 轉(zhuǎn) 實(shí)體對(duì)象皆撩。