Spring MVC的工作原理,我們來看看其原始碼實現

youzhibing2904發表於2019-06-06

前言

  開心一刻

    晚上陪老丈人吃飯,突然手機響了,我手賤按了擴音……哥們:快出來喝酒!哥幾個都在呢!我:今天不行,我現在陪老丈人吃飯呢。哥們:那你抓緊喝,我三杯白酒,把我岳父放倒了才出來的,你也快點。看著我老丈人的臉,我不知道該怎麼回了……

豬一樣的隊友

遺留問題

  在關於利用maven搭建ssm的部落格,我們一起來探討下問的最多的問題中,我遺留了一個問題:Spring mvc是何時、何地、如何將Model中的屬性繫結到哪個作用域,這裡的作用域指的是Servlet的四大作用域;不瞭解問題背景的可以回過頭去看看我的上篇博文。

  明確的解答我會放到最後,在解答問題之前,我先和大家一起來捋一捋Spring mvc的工作原理。廢話不多說,開始我們神祕的探險之旅!

應用示例

  在講工作原理之前,我們先看一個簡單的spring mvc(ssm)示例,以及實現的效果

  工程程式碼地址:ssm-web 

  工程結構與效果如上所示,我們不做過多的探究,我們打起精神往下看本篇的重點

工作原理

  準備 - 資源的載入與初始化

    1、DispatcherServlet 靜態初始化

      DispatcherServlet中有如下靜態塊

static {
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
    }

      這裡會將DispatcherServlet.properties中的內容讀取到DispatcherServlet的屬性:private static final Properties defaultStrategies中,DispatcherServlet.properties內容如下

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
View Code

      指定了DispatcherServlet策略介面的預設實現,後續DispatcherServlet初始化策略的時候會用到

    2、interceptor定義的載入

      spring啟動過程中會呼叫InterceptorsBeanDefinitionParser的parse方法來解析出我們自定義的interceptor定義,封裝成MappedInterceptor型別的bean定義,並放到spring容器中;我們可以簡單的認為spring容器中已經存在了我們自定義的interceptor的bean定義

    3、DispatcherServlet初始化策略:initStrategies

      DispatcherServlet的繼承圖如下

      DispatcherServlet是一個Servlet,tomcat啟動過程中會呼叫其init方法,一串的呼叫後,會呼叫DispatcherServlet的initStrategies方法

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

      例項化步驟1中的預設實現,並填充到DispatcherServlet各個屬性值中

    4、DefaultAnnotationHandlerMapping的攔截器初始化

      DispatcherServlet.properties種指定了兩個預設的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,這兩者的類繼承圖如下(我們暫時只關注DefaultAnnotationHandlerMapping)

      DefaultAnnotationHandlerMapping間接實現了ApplicationContextAware,那麼在DefaultAnnotationHandlerMapping例項初始化過程中,會呼叫setApplicationContext(ApplicationContext applicationContext)方法,一串呼叫後,會來到AbstractUrlHandlerMapping的initApplicationContext()

@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.mappedInterceptors);
    initInterceptors();
}

      初始化了DefaultAnnotationHandlerMapping的攔截器:interceptor

    我們來看下具體的初始化過程,看看上面的順序是否只是我個人的臆想?

    可以看到,初始化順序就是我們上面說的,不是我個人的意淫;此時的DefaultAnnotationHandlerMapping中有我們自定義的MyInterceptor。初始化過程我們需要關注的就是上述這些,下面我們一起看看具體請求的過程

  請求的處理

    請求從servlet的service開始,一路到DispatcherServlet的doDispatch,如下圖

    doDispatch

/**
 * Process the actual dispatching to the handler. 將請求分發到具體的handler,也就是我們的controller
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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;

            // Determine handler for the current request. 決定哪個handler來處理當前的請求
            // mappedHandler是由handler和interceptor集合組成的一個執行鏈,有點類似FilterChain
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request. 決定哪個adapter來處理當前的請求
            // handlerMapping是找出適配的handler,而真正回撥handler的是adapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    String requestUri = urlPathHelper.getRequestUri(request);
                    logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // handler的前置處理,也就是呼叫適配當前url的interceptor的preHandler方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            try {
                // Actually invoke the handler. 真正呼叫handler
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
            }

            applyDefaultViewName(request, mv);
            // handler的後置處理,也就是呼叫適配當前url的interceptor的postHandler方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        // 處理handler返回的結果,會呼叫適配當前url的interceptor的afterCompletion方法
        // 這裡會將響應結果返回給請求者
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            return;
        }
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}
View Code

      handlerMapping具體如何找到匹配當前url的handler(一般而言就是我們的controller)、handlerAdapter具體如何回撥真正的handler,有興趣的可以自行去跟下,我就不跟了。我們具體看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 這個與我們最初的疑問有關

    processDispatchResult

      可以看到model中的persons會被設定到request的attributes中,然後轉發請求到show_person.jsp,轉發過程中request作用域的變數仍然有效,所以show_person.jsp中的jstl標籤和el表示式能夠取到persons變數,最後將show_person.jsp中的內容填充好之後的靜態內容返回給請求者;至此就完成了一次請求的響應

問題解答

  回到我們開篇的疑問:Spring mvc是何時、何地、如何將Model中的屬性繫結到哪個作用域?想必大家已經知道答案了

  Controller中的model、ModelMap的注入由spring mvc完成,這個不是請求傳入的引數,用於繫結變數到Servlet作用域;預設情況下,在DispatcherServlet呼叫了真正的handler之後,將結果返回給請求者的過程中,將model、modelMap中的變數設定到了request的attributes中,轉發的過程中,request中的變數仍然有效,所以show_person.jsp中能取到persons這個變數,自此疑問得到解答

總結

  1、Spring MVC工作原理圖

    圖是用的別人的,具體是誰的我也不記得了(捂臉)

  2、DefaultAnnotationHandlerMapping在spring3.2中被廢棄,替換成了RequestMappingHandlerMapping

相關文章