當一個http請求來臨時,SpringMVC究竟偷偷幫你做了什麼?SpringMVC處理器適配器與處理器篇章

SpringMVC之請求處理適配器與處理器源碼分析

上次的SpringMVC源碼分析因爲篇幅原因只將請求映射器的源碼流程分析完畢,不知道大家對上次的流程分析有什麼意見呢?空閒的時候是不是自己追了一遍源碼嘞?

先上圖:

在這裏插入圖片描述

上一篇文章,我將 Handler處理器映射器做了一個很詳細流程分析,那麼本篇文章會圍繞處理器適配器處理器兩個流程來分析源碼!

1. 處理器適配器源碼解析

上篇文章其實後面也大概說了一下後面的大概邏輯,但是事實上,SpringMVC作爲一個優秀的框架,他所考慮的是很全面的,其實在開發一個Controller的方法不止只有一個加上@Controller一個方式,還有基於接口來實現的,比如實現Colltroller接口、實現HttpRequestHandler接口等操作,對於不同的處理方式,那麼對於不同的處理方式,SpringMVC是如何感知到的呢?因此,在SpringMVC根據請求路徑找到對應的對應的映射方法後如何判斷這個方法是根據上面三種那種方式創建出來的呢?此時處理器適配器就派上用場了!看一段代碼!

// 根據請求路徑獲取到映射方法的詳細信息
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
}

// 調用處理器適配器,找到該方法對應的處理器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

我們進入到處理器適配器裏面的邏輯去看一下

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a                                HandlerAdapter that supports this handler");
}
  • 首先他會循環一個叫做 handlerAdapters 的屬性,那麼這個屬性是在哪裏set的呢?在spring-webmvc.jar目錄下有一個叫做DisPatcherServlet.properties的文件,在內部定義了三個處理器,爲什麼是三個處理器呢?因爲上面說了,有三種控制器的編碼方式,所以會有三種對應的處理器!

在這裏插入圖片描述

  • 該方法會循環所有的適配器方案,直到直到合適的處理器,返回,否則就會拋出ServletException異常!

2. 處理器源碼解析

當這個處理器返回之後,下一步就是要拿着這個處理器處理我們對應的方法!如何處理呢?我們回到最初的org.springframework.web.servlet.DispatcherServlet#doDispatch方法

在這裏插入圖片描述

我們進入到處理器代碼邏輯內部org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal方法(注意這裏我們以常用的 以@Controller方式註冊的方式分析)

下面看一段代碼

// 查看是否需要走同步塊(一般情況下不會設置爲同步)
//session 是非線程安全的,如果需要保證用戶能夠在多次請求中正確的訪問同一個 session ,就要將 synchronizeOnSession 設置爲 TRUE 。
if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
        Object mutex = WebUtils.getSessionMutex(session);
        synchronized (mutex) {
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No HttpSession available -> no mutex necessary
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
}
else {
    // 我們點進去查看一下這個方法內部的實現
    mav = invokeHandlerMethod(request, response, handlerMethod);
}

invocableMethod.invokeAndHandle(webRequest, mavContainer);方法內部實現!

org.springframework.web.method.support.InvocableHandlerMethod#invokeForReques方法實現

  • 我們可以看到有這樣一段邏輯代碼
@Nullable
public Object invokeForRequest(NativeWebRequest request, 
                               @Nullable ModelAndViewContainer mavContainer,
                               Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
}
  • getMethodArgumentValues獲取方法的參數,以及傳遞的值
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
	//獲取方法的參數列表(形參列表)
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
	//構建參數對象數組
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        //拿到參數對象
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable 										resolver"));
        }
        try {
        //注意這個方法  重點這裏是通過方法的參數名稱 獲取request對象裏面對應的參數賦值給對應的參數對象
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, 
                                                     request, this.dataBinderFactory);
        }
        catch (Exception ex) {
 
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable()
                                                     .toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    //獲取到所有的參數和對應的參數的值之後,返回
    return args;
}

doInvoke(Object… args)

  • 處理器開始反射執行該方法,具體的執行的主要邏輯如下:
@Nullable
protected Object doInvoke(Object... args) throws Exception {
    ReflectionUtils.makeAccessible(getBridgedMethod());
    try {
        return getBridgedMethod().invoke(getBean(), args);
    }
    ...忽略..
}

得到該方法對象,從Bean工廠中拿到該對象實例,傳遞參數進行設置行該方法,並獲取方法的返回值!

拿到返回值之後,逐級返回,回到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle方法

在這裏插入圖片描述

該方法最終調用的是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    // Try even with null return value. ResponseBodyAdvice could get involved.
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

最終通過writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);將執行結果寫到頁面(其實之前Servlet開發也是這樣的邏輯,只不過SpringMvc進行了層層封裝優化了而已)!

不難看出,SpringMvc攔截請求到處理請求映射方法,雖然現在還沒有說完,但是可以小小的總結一下:

在這裏插入圖片描述


才疏學淺,如果文章中理解有誤,歡迎大佬們私聊指正!歡迎關注作者的公衆號,一起進步,一起學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章