1. 程式人生 > >[Java] SpringMVC工作原理之一:DispatcherServlet

[Java] SpringMVC工作原理之一:DispatcherServlet

一、DispatcherServlet 處理流程

在整個 Spring MVC 框架中,DispatcherServlet 處於核心位置,它負責協調和組織不同元件完成請求處理並返回響應工作。在看 DispatcherServlet 類之前,我們先來看一下請求處理的大致流程:

  1. Tomcat 啟動,對 DispatcherServlet 進行例項化,然後呼叫它的 init() 方法進行初始化,在這個初始化過程中完成了:
  2. 對 web.xml 中初始化引數的載入;建立 WebApplicationContext (SpringMVC的IOC容器);進行元件的初始化;
  3. 客戶端發出請求,由 Tomcat 接收到這個請求,如果匹配 DispatcherServlet 在 web.xml 中配置的對映路徑,Tomcat 就將請求轉交給 DispatcherServlet 處理;
  4. DispatcherServlet 從容器中取出所有 HandlerMapping 例項(每個例項對應一個 HandlerMapping 介面的實現類)並遍歷,每個 HandlerMapping 會根據請求資訊,通過自己實現類中的方式去找到處理該請求的 Handler (執行程式,如Controller中的方法),並且將這個 Handler 與一堆 HandlerInterceptor (攔截器) 封裝成一個 HandlerExecutionChain 物件,一旦有一個 HandlerMapping 可以找到 Handler 則退出迴圈;(詳情可以看 [Java]SpringMVC工作原理之二:HandlerMapping和HandlerAdpater
     這篇文章)
  5. DispatcherServlet 取出 HandlerAdapter 元件,根據已經找到的 Handler,再從所有 HandlerAdapter 中找到可以處理該 Handler 的 HandlerAdapter 物件;
  6. 執行 HandlerExecutionChain 中所有攔截器的 preHandler() 方法,然後再利用 HandlerAdapter 執行 Handler ,執行完成得到 ModelAndView,再依次呼叫攔截器的 postHandler() 方法;
  7. 利用 ViewResolver 將 ModelAndView 或是 Exception(可解析成 ModelAndView)解析成 View,然後 View 會呼叫 render() 方法再根據 ModelAndView 中的資料渲染出頁面;
  8. 最後再依次呼叫攔截器的 afterCompletion() 方法,這一次請求就結束了。

 

二、DispatcherServlet 原始碼分析

DispatcherServlet 繼承自 HttpServlet,它遵循 Servlet 裡的“init-service-destroy”三個階段,首先我們先來看一下它的 init() 階段。

1 初始化

1.1 HttpServletBean 的 init() 方法

DispatcherServlet 的 init() 方法在其父類 HttpServletBean 中實現的,它覆蓋了 GenericServlet 的 init() 方法,主要作用是載入 web.xml 中 DispatcherServlet 的 <init-param> 配置,並呼叫子類的初始化。下面是 init() 方法的具體程式碼:

複製程式碼

@Override
public final void init() throws ServletException {
    try {
        // ServletConfigPropertyValues 是靜態內部類,使用 ServletConfig 獲取 web.xml 中配置的引數
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        // 使用 BeanWrapper 來構造 DispatcherServlet
        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) {}
    // 讓子類實現的方法,這種在父類定義在子類實現的方式叫做模版方法模式
    initServletBean();
}  

複製程式碼

 

1.2 FrameworkServlet 的 initServletBean() 方法

在 HttpServletBean 的 init() 方法中呼叫了 initServletBean() 這個方法,它是在 FrameworkServlet 類中實現的,主要作用是建立 WebApplicationContext 容器(有時也稱上下文),並載入 SpringMVC 配置檔案中定義的 Bean 到改容器中,最後將該容器新增到 ServletContext 中。下面是 initServletBean() 方法的具體程式碼:

複製程式碼

@Override
protected final void initServletBean() throws ServletException {
    try {
        // 初始化 WebApplicationContext (即SpringMVC的IOC容器)
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    } catch (ServletException ex) {
    } catch (RuntimeException ex) {
    }
}

複製程式碼

WebApplicationContext 繼承於 ApplicationContext 介面,從容器中可以獲取當前應用程式環境資訊,它也是 SpringMVC 的 IOC 容器。下面是 initWebApplicationContext() 方法的具體程式碼:

複製程式碼

protected WebApplicationContext initWebApplicationContext() {
    // 獲取 ContextLoaderListener 初始化並註冊在 ServletContext 中的根容器,即 Spring 的容器
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // 因為 WebApplicationContext 不為空,說明該類在構造時已經將其注入
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 將 Spring 的容器設為 SpringMVC 容器的父容器
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
      // 如果 WebApplicationContext 為空,則進行查詢,能找到說明上下文已經在別處初始化。
      wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果 WebApplicationContext 仍為空,則以 Spring 的容器為父上下文建立一個新的。
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        // 模版方法,由 DispatcherServlet 實現
        onRefresh(wac);
    }
    if (this.publishContext) {
        // 釋出這個 WebApplicationContext 容器到 ServletContext 中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

複製程式碼

下面是查詢 WebApplicationContext 的 findWebApplicationContext() 方法程式碼:

複製程式碼

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

複製程式碼

 

1.3 DispatcherServlet 的 onRefresh() 方法

建立好 WebApplicationContext(上下文) 後,通過 onRefresh(ApplicationContext context) 方法回撥,進入 DispatcherServlet 類中。onRefresh() 方法,提供 SpringMVC 的初始化,具體程式碼如下:

複製程式碼

    @Override
    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);
    }

複製程式碼

在 initStrategies() 方法中進行了各個元件的初始化,先來看一下這些元件的初始化方法,稍後再來詳細分析這些元件。

1.3.1 initHandlerMappings 方法

initHandlerMappings() 方法從 SpringMVC 的容器及 Spring 的容器中查詢所有的 HandlerMapping 例項,並把它們放入到 handlerMappings 這個 list 中。這個方法並不是對 HandlerMapping 例項的建立,HandlerMapping 例項是在上面 WebApplicationContext 容器初始化,即 SpringMVC 容器初始化的時候建立的。

複製程式碼

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // 從 SpringMVC 的 IOC 容器及 Spring 的 IOC 容器中查詢 HandlerMapping 例項
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // 按一定順序放置 HandlerMapping 物件
            OrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    // 如果沒有 HandlerMapping,則載入預設的
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

複製程式碼

1.3.2 initHandlerAdapters 方法

複製程式碼

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            OrderComparator.sort(this.handlerAdapters);
        }
    } else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        } catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }
    // Ensure we have at least some HandlerAdapters, by registering
    // default HandlerAdapters if no other adapters are found.
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
    }
}

複製程式碼

  

2 處理請求

HttpServlet 提供了 doGet()、doPost() 等方法,DispatcherServlet 中這些方法是在其父類 FrameworkServlet 中實現的,程式碼如下:

複製程式碼

@Override
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() 方法,我們來看一下程式碼:

複製程式碼

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 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);
    }
}

複製程式碼

DispatcherServlet 的 doService() 方法主要是設定一些 request 屬性,並呼叫 doDispatch() 方法進行請求分發處理,doDispatch() 方法的主要過程是通過 HandlerMapping 獲取 Handler,再找到用於執行它的 HandlerAdapter,執行 Handler 後得到 ModelAndView ,ModelAndView 是連線“業務邏輯層”與“檢視展示層”的橋樑,接下來就要通過 ModelAndView 獲得 View,再通過它的 Model 對 View 進行渲染。doDispatch() 方法如下:

複製程式碼

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    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 物件中。
            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);
        }
    }
}  

複製程式碼