1. 程式人生 > >說說SpringMVC從http流到Controller介面引數的轉換過程

說說SpringMVC從http流到Controller介面引數的轉換過程

### 一,前言 談起springMVC框架介面請求過程大部分人可能會這樣回答:負責將請求分發給對應的handler,然後handler會去呼叫實際的介面。核心功能是這樣的,但是這樣的回答未免有些草率。面試過很多人,大家彷佛約定好了的一般,給的都是這樣"泛泛"的標準答案。最近開發遇到了這樣的兩個場景: * 1>,上游的回撥介面要求接受型別為application/x-www-form-urlencode,請求方式post,接受訊息為xml文字。 * 2>,對接系統動態生成檔案(檔案實時變更,採用chunk編碼),導致業務系統無法預覽檔案(瀏覽器會直接下載),採用中轉介面對檔案流進行轉發。 針對上述需求,如何開發rest風格的介面解決呢? ### 二、request的生命週期 我們知道,當一個請求到達後端web應用(mvc架構的應用)監聽的埠, 率先被攔截器攔截到,然後轉交到對應的介面。我們知道底層的資料必定是資料流形式的,那麼他是怎麼把流轉成介面需要的引數,從而發起呼叫的呢?此時我們便需要去研究DispathServlet的處理邏輯了。 #### 2.1 DispatchServlet具備的職能 * handler 容器 * handler 前、後置處理器 * 請求轉發(交由HandlerApdater.handler()執行) * 響應結果轉發 具體入口程式碼如下(DipatchServlet.doDispatch): ``` protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 找到與請求匹配的handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 找到與請求匹配的HandlerAdpater HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // ... 省略部分程式碼 // handler 前置處理器 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // handler 呼叫: 會實際呼叫到我們的controller介面 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // handler 後置處理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // 返回結果分發 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { // 省略部分程式碼 } } ``` 這個介面就是我們尋常所說的handler的轉發邏輯。但是我們也知道了實際上去呼叫我們controller介面的是HandlerAdapter #### 2.2 HandlerAdapter具備的職能 從上述我們知道了請求的轉發過程,現在我們要弄清楚handler怎麼呼叫到我們的controller介面的(以RequestMappingHandlerAdapter為例)。 * argumentResolvers 引數解析器,提供了supportsParameter()、resolveArgument()兩個方法來告訴容器是否能解析該引數以及怎麼解析 * returnValueHandlers 返回值解析器, * modelAndViewResolvers 模型檢視解析器 * messageConverters 訊息轉換器, 跟蹤原始碼發現(RequestMappingHandlerAdapter.invokeHandlerMethod()),他呼叫Controller介面發生再ServletInvocableHandlerMethod.invokeAndHandle()方法。看一下主體邏輯: ``` public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 呼叫controller介面 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // ... 省略部分程式碼 try { // 處理返回結果 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } ``` 呼叫controller介面的方法跟蹤原始碼會發現,主要是通過request尋找到正確的引數解析器,然後去解析引數,這裡我們以@RequestBody標註的引數為例,看其是如何解析的: (RequestResponseBodyMethodProcessor.readWithMessageConverters()) ``` protected