SpringMvc方法獲取和返回引數原始碼探究
簡介
SpringMvc的方法裡我們可以接受各式各樣型別的引數,Stirng、Integer、@RequestBody(Json)、ModelAndView(Spring自動注入的一些引數)等,那麼SpringMvc是如何將這些引數注入的呢?
例子
分別用postman訪問上面4個介面
除了第一個介面呼叫成功,另外三個介面均報錯了。為什麼會報錯呢?@RequestBody、@RequestParam應該在什麼情況下使用呢?為什日期格式無法轉換呢?
SpringMvc原始碼
兩個介面分別對應請求方法引數的處理、響應返回值的處理,分別是HandlerMethodArgumentResolver
SpringMvc請求的入口是DispatcherServlet
進入到父類AbstractHandlerMethodAdapter中
進入到RequestMappingHandlerAdapter類中跟進handleInternal方法
進入到ServletInvocableHandlerMethod類
進入InvocableHandlerMethod類
進入HandlerMethodArgumentResolverComposite類
methodArgumentResolver.supportsParameter(parameter) 這個方法就是判斷當前遍歷的resolver和引數是不是匹配,不通的reslover都有自己的實現。當找到對應的resovler後,就用resolver去處理引數了。我們再回到之前的程式碼。
我們通過原始碼發現,RequestResponseBodyMethodProcessor這個類其實同時實現了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver這兩個介面。所以這裡實際上呼叫是該類的方法,我們繼續跟進。
處理請求的時候使用內部的readWithMessageConverters方法。
到這裡處理入參的流程就結束了,總結一下就是先找到對應的reslover,然後用reslover去處理引數。
下面來我們來看看常用的HandlerMethodArgumentResolver實現類(本文粗略講下,有興趣的讀者可自行研究)。
1. RequestParamMethodArgumentResolver
支援帶有@RequestParam註解的引數或帶有MultipartFile型別的引數
2. RequestParamMapMethodArgumentResolver
支援帶有@RequestParam註解的引數 && @RequestParam註解的屬性value存在 && 引數型別是實現Map介面的屬性
3. PathVariableMethodArgumentResolver
支援帶有@PathVariable註解的引數 且如果引數實現了Map介面,@PathVariable註解需帶有value屬性
4. MatrixVariableMethodArgumentResolver
支援帶有@MatrixVariable註解的引數 且如果引數實現了Map介面,@MatrixVariable註解需帶有value屬性
5. RequestResponseBodyMethodProcessor
本文已分析過
6. ServletRequestMethodArgumentResolver
引數型別是實現或繼承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod這些類。
(這就是為何我們在Controller中的方法裡新增一個HttpServletRequest引數,Spring會為我們自動獲得HttpServletRequest物件的原因)
7. ServletResponseMethodArgumentResolver
引數型別是實現或繼承或是ServletResponse、OutputStream、Writer這些類
8. RedirectAttributesMethodArgumentResolver
引數是實現了RedirectAttributes介面的類
9. HttpEntityMethodProcessor
引數型別是HttpEntity
從名字我們也看的出來, 以Resolver結尾的是實現了HandlerMethodArgumentResolver介面的類,以Processor結尾的是實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的類。
在回來看看文章開頭出現的3個介面呼叫錯誤
1.在我們呼叫使用了@RequestBody(tes/request)註解的介面時,Spring會指定RequestResponseBodyMethodProcessor進行處理,而我們由於沒有指定content-type,postman裡預設為multipart/form-data,而該reslover只能對application/json格式的引數進行處理,所以會報Unsupported Media Type。我們將引數改成json格式,然後設定content-type為application/json就能正常呼叫了。
2. test/requestParam方法以及地址https://127.0.0.1:9090/mybatis/test/requestParam?id=1&name=eragon&age=23,這個請求會找到RequestParamMethodArgumentResolver(使用了@RequestParam註解)。RequestParamMethodArgumentResolver在處理引數的時候使用request.getParameter(引數名)即request.getParameter("user")得到,很明顯我們的引數傳的是id=1&name=eragon&age=23。因此得到null,RequestParamMethodArgumentResolver處理missing value會觸發MissingServletRequestParameterException異常。
3.test/date方法中,會呼叫RequestParamMethodArgumentResolver進行處理,同樣spring會載入兩個,一個useDefaultResolution的值為true,一個為false。因為是Date型別,會通過request.getParameter("date")獲得引數值,然後通過DataBinder找到對應的converter去處理(如果沒有對應型別自定義CustomDateEditor),預設是ObjectToObject處理sourceType=String,targetType=Date型別的轉換。由於該轉換器並不支援yyyy-MM-dd的時間轉換,所以會報IllegalArgumentException。解決方法有幾種:
1)將前段傳值改為UTC標準時間格式?date=Sat, 17 May 2014 16:30:00 GMT
2)自定義日期類轉換器
@Configuration
public class DateConvertConfig {
@Bean
public DateConverter getDateConverter(){
return new DateConverter();
}
}
public class DateConverter implements Converter<String,Date> {
@Override
public Date convert(String s) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
3) 自定義屬性編輯器
@InitBinder
public void dateBind(WebDataBinder binder){
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(sf, false));
}
總結
在使用SpringMvc時,介面中的入參和返回值可以選擇不同的型別以及使用Spring提供的註解,本文就是探究一下不同型別的入參和返回值是如何被Spring注入和返回的。