[Java]SpringMVC工作原理之一:DispatcherServlet
一、DispatcherServlet 處理流程
在整個 Spring MVC 框架中,DispatcherServlet 處於核心位置,它負責協調和組織不同組件完成請求處理並返回響應工作。在看 DispatcherServlet 類之前,我們先來看一下請求處理的大致流程:
- Tomcat 啟動,對 DispatcherServlet 進行實例化,然後調用它的 init() 方法進行初始化,在這個初始化過程中完成了:1) 對 web.xml 中初始化參數的加載、2) 建立 WebApplicationContext (上下文)、3) 初始化了九大組件;
- 客戶端發出請求,由 Tomcat 接收到這個請求,如果匹配 DispatcherServlet 在 web.xml 中配置的映射路徑,Tomcat 就將請求轉交給 DispatcherServlet 處理;
- DispatcherServlet 取出 HandlerMapping 組件,根據請求信息,通過 HandlerMapping 所存儲的配置找到處理該請求的 Handler (處理器),並且將這個 Handler 與一堆 HandlerInterceptor (攔截器) 封裝成一個 HandlerExecutionChain 對象;
- DispatcherServlet 取出 HandlerAdapter 組件,根據已經找到的 Handler,再從所有 HandlerAdapter 中找到可以處理該 Handler 的 HandlerAdapter 對象;
- 執行 HandlerExecutionChain 中所有攔截器的 preHandler() 方法,然後再利用 HandlerAdapter 執行 Handler ,執行完成得到 ModelAndView,再依次調用攔截器的 postHandler() 方法;
- 利用 ViewResolver 將 ModelAndView 或是 Exception(可解析成 ModelAndView)解析成 View,然後 View 會調用 render() 方法再根據 ModelAndView 中的數據渲染出頁面;
- 最後再依次調用攔截器的 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 {// 設置初始化參數到 bean 中 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(上下文) 容器,並註冊到 ServletContext 中。下面是 initServletBean() 方法的具體代碼:
@Override protected final void initServletBean() throws ServletException {try { // 初始化 WebApplicationContext this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { } catch (RuntimeException ex) { } }
WebApplicationContext 繼承於 ApplicationContext 接口,ApplicationContext 是 Spring 容器的上下文,從上下文中可以獲取當前應用程序環境信息。下面是 initWebApplicationContext() 方法的具體代碼:
protected WebApplicationContext initWebApplicationContext() { // 獲取 ContextLoaderListener 初始化並註冊在 ServletContext 中的根上下文 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) { // 將 rootContext 上下文設為它的父上下文 cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // 如果 WebApplicationContext 為空,則進行查找,能找到說明上下文已經在別處初始化。 wac = findWebApplicationContext(); } if (wac == null) { // 如果 WebApplicationContext 仍為空,則以 rootContext 為父上下文建立一個新的。 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() 方法中進行了各個組件的初始化,稍後再來分析這些組件。
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); } } }
[Java]SpringMVC工作原理之一:DispatcherServlet