簡單看看@RequestBody註解原理

       又到了很無聊的時候了,於是隨便看看源碼假裝自己很努力的樣子,哈哈哈;

  記得上一篇博客隨便說了一下RequestBody的用法以及注意的問題,這個註解作為非常常用的註解,也是時候了解一波其中的原理了。

    溫馨提示:閱讀本篇博客,默認你之前大概看過springmvc源碼,懂得其中的基本流程

1.HttpMessageConverter接口

  這個接口就是@RequestBody和@ResponseBody這兩個註解的精髓,我們就先看看這個頂層接口定義了哪些方法:

public interface HttpMessageConverter<T> {

    //判斷當前轉換器是否可以解析前端傳過來的數據
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

    //判斷當前轉換器是否可以將後端數據解析為前端需要的格式
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

    //當前轉換器能夠解析所有的數據類型
    List<MediaType> getSupportedMediaTypes();

    //這個方法就是讀取前端傳過來的數據
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

    //將後台數據轉換然後返回給前端
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

}

   

  我們從這個頂層接口中這幾個方法就大概能看到一些東西,可以肯定的就是在@RequestBody和@ResponseBody這兩個註解原理的內部轉換器應該都是實現了這個HttpMessageConverter,因為這個接口裡又是read,又是write;然後就是在上面我只是簡單說了前端傳過來的數據,返回給前端需要的格式這種模糊的說法,為什麼不直接說返回json格式的數據呢?

  哈哈,可能有的小夥伴會說,瑪德,你絕逼是怕說錯,才說這些模糊的說法;咳,當然有部分是這個意思,但是最大的原因就是上面方法參數中有個類MediaType,你打開看看就知道了,這裏定義了很多的可以解析的數據類型,比如”application/json”,”application/xml”,”image/gif”,”text/html”….等等,還有好多聽都沒聽過的;

  其實了解http請求的小夥伴應該已經看出來了,這裏這些數據類型就是下圖所示的這些;當然,我們暫時只關注json的,至於其他類型的怎麼解析有興趣的小夥伴可以研究研究;

 

 2.HandlerMethodArgumentResolver接口

  我們看看這個接口,看名字就知道應該是方法參數解析器,很明顯就是用於解析方法Controller中方法的參數的,還是簡單看看這個接口中的方法:

public interface HandlerMethodArgumentResolver {

    //該解析器是否支持解析Controller中方法中的參數,因為這裏參數類型可以是簡單類型,也可以是集合等類型
    boolean supportsParameter(MethodParameter parameter);

    //開始解析Http請求中的數據,解析出來的數據要和方法參數對應
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

   這個接口定義的方法作用其實就是將http請求中的參數對應到Controller中的參數

 

3.HandlerMethodReturnValueHandler接口 

public interface HandlerMethodReturnValueHandler {

    //這個方法判斷該處理器是否支持返回值類型,這裏的返回值就是controller方法執行后的返回值
    boolean supportsReturnType(MethodParameter returnType);

    //將controller方法的返回值進行解析成前端需要的格式,後續就會丟給前端
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

   這個接口的作用:比如一個Controller方法中的返回值是一個集合,那麼springmvc內部在將數據返回給前端之前,就會先拿到所有的返回值解析器,然後遍歷每一個,分別執行supportsReturnType方法,看看哪個解析器可以解析集合類型,找到解析器之後,然後再執行handleReturnValue方法解析就行了,其實2中的方法參數解析器也是這樣的一個步驟

 

4.ServletInvocableHandlerMethod類

  這個類是干什麼的呢?看過springmvc源碼的人應該知道一點,還是簡單說說吧,在springmvc中會將controller中的每個被RequestMapping註解修飾的方法(也可以叫做處理器)給封裝成ServletInvocableHandlerMethod類,封裝后想要執行該處理器方法只需要執行該類的invokeAndHandle方法;

  請注意:就是在invokeAndHandle這個方法中會調用:調用方法參數解析器——>執行處理器方法——–>調用返回值解析器、

  可以簡單看看源碼,請一定要了解springmvc的流程,因為我不會從頭到尾講一遍,我們直接從DispatcherServlet中的doDispatch方法的ha.handle(xxx)這裏說起,這裏主要是執行處理器適配器的handle方法,這裏具體的處理器適配器實現是:AbstractHandlerMethodAdapter

  

  我們進入AbstractHandlerMethodAdapter這個適配器的handle方法看看(^o^)/:

//可以看到這裏就是調用了handleInternal方法,而handleInternal方法未實現
public final ModelAndView handle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }


//這個方法在子類RequestMappingHandlerAdapter中實現
protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception;

 

  接下來我們看看RequestMappingHandlerAdapter中實現的handleInternal方法(」゜ロ゜)」:

protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception {

        //省略跟邏輯無關的代碼
                .......
               .........
        
        return invokeHandlerMethod(request, response, handlerMethod);
    }

 

  進入invokeHandlerMethod方法看看(´・_・`):

private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception {
        
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //省略一些代碼

        //就是再這裏去執行Controller中的處理器方法
        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

        //此處省略好多代碼
    }

 

  繼續進入到invokeAndHandle方法內部看看╮(╯_╰)╭:

public final void invokeAndHandle(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
         //請注意,這裏面就是執行handler方法的位置
        Object returnValue = invokeForRequest(request, mavContainer, providedArgs);

        //省略一些代碼              

        try {
            //這裏就是執行我們前面說的返回值解析器
            returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
        } 
       //省略一些代碼
    }

 

  看看invokeForRequest方法你就能看到有趣的東西ヽ(”`▽´)ノ

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
                //這裏就是從request中拿到參數,利用方法參數解析器進行解析,映射到方法參數中,這個方法就在下面
                Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        
               //省略一些代碼
        
                //這裏就是根據上一步將請求參數映射到處理器方法參數中,然後執行對應的處理器方法
               Object returnValue = doInvoke(args);
        
               //省略一些代碼
        
                return returnValue;
    }


private Object[] getMethodArgumentValues(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
          //這裏獲取匹配到的handler方法的參數數組,每個參數在之前都被封裝成了一個MethodParameter對象,然後再遍歷這個數組,將其中每個MethodParameter和http請求提供的參數進行比較,
至於怎麼比較,就會用到之前說的參數解析器的那個support方法
MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } //省略一些代碼 return args; }

 

     其實到這裏,有木有感覺清晰一點了,那麼肯定有小夥伴要問了,說了半天,你還是沒有說json是怎麼解析的啊?

  不要急,我們先把大概的流程過一遍之後,後面的都是小問題,那麼,我們的轉換器是在哪裡轉換的呢?

  欲知後事如何,請往後面看

 

5.無題

  在4中我們重點看兩個地方,第一個地方:this.argumentResolvers.supportsParameter(parameter);第二個地方:this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

  5.1.RequestResponseBodyMethodProcessor

    第一個地方,我們點進去supportsParameter方法,

   

    

  然後我們進入getArgumentResolver方法內部(´□`川):

@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
             //for循環遍歷所有的方法參數解析器,
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                
//省略一些代碼
//判斷哪一個解析器支持解析request中的參數,這裏很關鍵,因為眾多解析器中其中有一個參數解析器是RequestResponseBodyMethodProcessor,
          //下面我們看看這個解析器的supportParameter方法
if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }

  

  RequestResponseBodyMethodProcessor的supportsParameter方法,這裏想必能看得懂吧,就是看Controller中的處理器方法中的參數前面有沒有RequestBody註解,有註解,那麼這個RequestResponseBodyMethodProcessor解析器就會生效

  對了,補充一點,這個解析器RequestResponseBodyMethodProcessor可是同時實現了方法參數解析器接口、返回參數解析器接口的哦,這說明了處理返回值解析器用的也是這個解析器

 

  5.2.執行argumentResolvers.resolveArgument()方法

//這個方法就到了最關鍵的地方了,注意了注意了
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 

  throws Exception { //獲取一個轉換器,用於讀取前端傳過來的json數據 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());

//獲取handler方法形參中的所有註解,例如@Valid。@PathVariable等 Annotation[] annotations = parameter.getParameterAnnotations();

     for (Annotation annot : annotations) {       //判斷如果是以valid開頭的註解,其實就是@Valid註解或者是@Validated註解,那麼就會去校驗是否符合規則嘛,這個不用多說         if(annot.annotationType().getSimpleName().startsWith("Valid")) { String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); BindingResult bindingResult = binder.getBindingResult(); if (bindingResult.hasErrors()) { throw new MethodArgumentNotValidException(parameter, bindingResult); } } } return arg; }

  最後我們只需要輕輕點開readWithMessageConverters方法,就能看到更有意思的東西~^o^~

 

6.xxxConverter

  我們進入readWithMessageConverters這個方法,

@SuppressWarnings("unchecked")
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException,
            HttpMediaTypeNotSupportedException {
            //這裏回答了本篇最開始說的MediaType 到底是什麼東西,從哪裡獲取的,很明顯是從請求頭中的ContentType獲取的
             MediaType contentType = inputMessage.getHeaders().getContentType();
            if (contentType == null) {
                 contentType = MediaType.APPLICATION_OCTET_STREAM;
            }
            //獲取所有的HttpMessageConverter,遍歷,看看哪一個轉換器支持前端傳過來的數據類型
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                 if (messageConverter.canRead(paramType, contentType)) {
//調用對應的轉換器的read方法去把前端傳過來的json字符串轉為java對象
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage); } } //省略一些代碼 }

  

   到了這裏肯定有人會說,那到底用的是哪一個轉換器呢?難道是要我們自己導入?還是默認已經導入轉換器了呢?

  當然是默認就為你初始化了一些轉換器了啊,如果你想自定義也行,而且仔細看看下圖中跟json有關的只有MappingJackson2HttpMessageConverter這個轉換器了,可想而知這個轉換器(實際上是這個轉換器的父類方法中才有具體的操作)中使用的就是開源的Jackson來將json字符串轉為java對象的,有興趣了解的可以使用一下Jackson自己嘗試一下;

  偷偷告訴你(҂ ˘ _ ˘ ),springboot默認已經導入了Jackson包,如果你後期想用其他的轉換器,只需要導入相關依賴就ok了;

  

 

 

7.結束

  這篇博客到這裏就差不多了,寫了好久,邊寫邊查資料,可以說每次看源碼都能學到新的東西,當然,我也沒有死磕精神,不是不想,主要是沒有那個水平,哈哈;

  其實還要寫還能寫,不是還有個@ResponseBody原理還沒說嗎?其實跟@RequestBody大同小異的,有興趣的可以在第4點最後的代碼中返回值參數處理器方法,這裏就是入口

  話說還有個問題沒有解決,本來我想找一下最後的那幾個轉換器初始化時機的,然後實在是找不出來啊,查了一下資料,都說是在處理器適配器的構造器中初始化這些轉換器的,哎,jdk1.8,我在適配器中找了好久,愣是沒找到,打斷點也調試不出來,把自己坑了好久;

  有沒有大哥知道初始化時機的,評論一下,謝謝了(ㄒoㄒ)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

您可能也會喜歡…