說說SpringMVC從http流到Controller介面引數的轉換過程
阿新 • • 發佈:2021-03-12
### 一,前言
談起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