1. 程式人生 > >SpringMvc方法獲取和返回引數原始碼探究

SpringMvc方法獲取和返回引數原始碼探究

簡介

SpringMvc的方法裡我們可以接受各式各樣型別的引數,Stirng、Integer、@RequestBody(Json)、ModelAndView(Spring自動注入的一些引數)等,那麼SpringMvc是如何將這些引數注入的呢?

例子

分別用postman訪問上面4個介面

除了第一個介面呼叫成功,另外三個介面均報錯了。為什麼會報錯呢?@RequestBody、@RequestParam應該在什麼情況下使用呢?為什日期格式無法轉換呢?

SpringMvc原始碼

兩個介面分別對應請求方法引數的處理、響應返回值的處理,分別是HandlerMethodArgumentResolver

HandlerMethodReturnValueHandler,這兩個介面都是Spring3.1版本之後加入的。

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注入和返回的。