1. 程式人生 > >SpringMVC工作流程及程式碼分析

SpringMVC工作流程及程式碼分析

  每談到SpringMVC的工作流程,首先接觸到的就是下面這個圖。從這個圖可以大致明白SpringMVC是如何工作的。但是我是一個喜歡探究來龍去脈的人,如果不告訴我為什麼這麼做,單單知道流程就是這樣,抱歉,我真的記不住,更不用提裡面這麼多專業名詞了。所以,通過翻閱了原始碼,大致知道流程是具體怎麼實現的,也學到了一些新的設計模式,所以我將閱讀原始碼的所得記錄下來,希望本文可以幫助到和我一樣喜歡探究來龍去脈的人。

1,SpringMVC是如何被初始化的?

  是通過在web.xml配置檔案中配置servlet,servlet的class指向org.springframework.web.servlet.DispatcherServlet,並匹配指定的url(一般情況下使用“/”匹配所有url),指定的請求都會進入DispatcherServlet,由DispatcherServlet進行處理。

<!-- spring mvc核心:分發servlet -->
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- spring mvc的配置檔案 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

2,DispatcherServlet的例項化

  為什麼要講DispatcherServlet的初始化,而不是直接說SpringMVC的工作流程,因為在初始化的過程中,建立了HandlerMappings,HandlerAdapters,我會在下面描述這兩個如何定義及使用的,有了這些概念,再接觸SpringMVC的工作流程,就會非常清晰流暢了。

  Tomcat啟動時,就會對DispatcherServlet進行例項化,呼叫他的init()方法。這裡並沒有直接在DispatcherServlet中重寫init(),而是使用了模板方法模式。抽象類HttpServletBean中定義並實現了init(),FrameworkServlet繼承了HttpServletBean,DispatcherServlet繼而繼承了FrameworkServlet。如果對於模板方法模式不瞭解,可以檢視我的另外一篇文章

模板方法模式(Template Method)及應用,也可以自行搜尋學習,這裡不再贅述。

public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.讓子類去實現的方法
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

 2.1 FrameworkServlet的initServeltBean()

@Override
protected final void initServletBean() throws ServletException {
    ...
    try {
        // 初始化 WebApplicationContext (即SpringMVC的IOC容器)
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();  //這個沒有具體實現,如果後續發現會補上
    } catch (ServletException ex) {
    } catch (RuntimeException ex) {
    }
    ...           
}

  2.2 FrameworkServlet的initServeltBean()

protected WebApplicationContext initWebApplicationContext() {
     //獲取在web.xml中定義的監聽器ContextLoaderListener,初始化並註冊在ServeltBean中的根容器,即Spring容器 WebApplicationContext rootContext
= WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it  因為webApplicationContext不為空,說明在構造時已經注入。
wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext);    //將Spring容器設為SpringMVC容器的父容器,這也就是為什麼,SpringMVC容器可以呼叫Spring容器而反過來不可以。 } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = findWebApplicationContext();  //如果為空,則進行查詢,能找到就說明上下文已在別處初始化,詳見2.3 } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext);  //如果webApplicationContext仍為空,則以Spring的容器作為父容器建立一個新的。
}
if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac);  //模板方法,由DispatcherServlet實現,詳見2.4 } if (this.publishContext) { // 釋出這個webApplicationContext容器到ServeltContext中。 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }

 

 2.3 查詢webApplicationContext: findWebApplicationContext();

  在servletContext中根據attrName查詢

    protected WebApplicationContext findWebApplicationContext() {
        String attrName = getContextAttribute();
        if (attrName == null) {
            return null;
        }
     //從ServletContext中查詢已釋出的容器 WebApplicationContext wac
= WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }

 

 2.4 DispatcherServelt 中的 

  從原始碼中可以看到,在這個方法中,主要是對各個元件的初始化,包括在整個流程中非常重要的 處理器對映器(HandlerMapping) 和 處理器介面卡(HandlerAdapter)

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

 

2.4.1 HanderMapping是什麼?

2.4.2 HanderAdapter是什麼?

 

3 DispatcherServelt處理請求

  根據Servlet處理請求的流程,是要呼叫其doGet(),doPost()方法。DispatcherServelt本質也是個Servlet,所以這兩個方法是在其父類FrameworkServelt中實現的。

3.1 FrameworkServelt中的doGet(),doPost()

  protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

 

這些方法都統一呼叫了processRequest(request, response);

 

3.2  processRequest(request, response)

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    // 返回與當前執行緒相關聯的 LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 根據請求構建 LocaleContext,公開請求的語言環境為當前語言環境
    LocaleContext localeContext = buildLocaleContext(request);
    
    // 返回當前繫結到執行緒的 RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    // 根據請求構建ServletRequestAttributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
//WebAsyncManager:用來管理非同步請求的處理。什麼時候要用到非同步處理呢?就是業務邏輯複雜(或者其他原因),為了避免請求執行緒阻塞,需要委託給另一個執行緒的時候。
    // 獲取當前請求的 WebAsyncManager,如果沒有找到則建立
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);    
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 使 LocaleContext 和 requestAttributes 關聯
    initContextHolders(request, localeContext, requestAttributes);

    try {
        // 由 DispatcherServlet 實現
        doService(request, response);
    } catch (ServletException ex) {
    } catch (IOException ex) {
    } catch (Throwable ex) {
    } finally {
        // 重置 LocaleContext 和 requestAttributes,解除關聯
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }// 釋出 ServletRequestHandlerEvent 事件
        publishRequestHandledEvent(request, startTime, failureCause);
    }
}

 

 3.3 DispatcherServetl 中的 doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;    //processedRequest:加工過的請求
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;        //上傳相關的?後續補充
    // 獲取當前請求的WebAsyncManager,如果沒找到則建立並與請求關聯
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            // 檢查是否有 Multipart,有則將請求轉換為 Multipart 請求
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // 遍歷所有的 HandlerMapping 找到與請求對應的 Handler,並將其與一堆攔截器封裝到 HandlerExecution 物件中。(如果對handler不理解,不要急,到第二章去看)
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 遍歷所有的 HandlerAdapter,找到可以處理該 Handler 的 HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 處理 last-modified 請求頭 
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            // 遍歷攔截器,執行它們的 preHandle() 方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            try {
                // 執行實際的處理程式
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            } finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
            }
            applyDefaultViewName(request, mv);
            // 遍歷攔截器,執行它們的 postHandle() 方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception ex) {
            dispatchException = ex;
        }
        // 處理執行結果,是一個 ModelAndView 或 Exception,然後進行渲染
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
    } catch (Error err) {
    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // 遍歷攔截器,執行它們的 afterCompletion() 方法  
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            return;
        }
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}  

 

  通過閱讀原始碼,已經可以整理出最開始的流程圖和原始碼之間的對應了,還有一些東西有空會補充上。知其然,更要知其所以然,才是與別人拉開差距的真正意義。