在前面的文章中,我較爲詳細的分析了 Spring IOC 和 AOP 部分的源碼,並寫成了文章。爲了讓個人 Spring 源碼分析系列文章更爲豐富一些,因此從本篇文章開始,我未來向你們介紹一下 Spring MVC 的一些原理。在本篇文章中,你將會了解到 Spring MVC 處理請求的過程。同時,你也會了解到 Servlet 相關的知識。以及 Spring MVC 的核心 DispatcherServlet 類的源碼分析。在掌握以上內容後,相信你們會對 Spring MVC 的原理有更深的認識。html
若是你們對上面介紹的知識點感興趣的話,那下面不妨和我一塊兒來去探索 Spring MVC 的原理。Let`s Go。java
在探索更深層次的原理以前,咱們先來了解一下 Spring MVC 是怎麼處理請求的。弄懂了這個流程後,才能更好的理解具體的源碼。這裏我把 Spring MVC 處理請求的流程圖畫了出來,一塊兒看一下吧:web
如上,每個重要的步驟上面都有編號。我先來簡單分析一下上面的流程,而後再向你們介紹圖中出現的一些組件。咱們從第一步開始,首先,用戶的瀏覽器發出了一個請求,這個請求通過互聯網到達了咱們的服務器。Servlet 容器首先接待了這個請求,並將該請求委託給 DispatcherServlet 進行處理。接着 DispatcherServlet 將該請求傳給了處理器映射組件 HandlerMapping,並獲取到適合該請求的攔截器和處理器。在獲取處處理器後,DispatcherServlet 還不能直接調用處理器的邏輯,須要進行對處理器進行適配。處理器適配成功後,DispatcherServlet 經過處理器適配器 HandlerAdapter 調用處理器的邏輯,並獲取返回值 ModelAndView。以後,DispatcherServlet 須要根據 ModelAndView 解析視圖。解析視圖的工做由 ViewResolver 完成,若能解析成功,ViewResolver 會返回相應的視圖對象 View。在獲取到具體的 View 對象後,最後一步要作的事情就是由 View 渲染視圖,並將渲染結果返回給用戶。spring
以上就是 Spring MVC 處理請求的全過程,上面的流程進行了必定的簡化,好比攔截器的執行時機就沒說。不過這並不影響你們對主過程的理解。下來來簡單介紹一下圖中出現的一些組件:瀏覽器
組件 | 說明 |
---|---|
DispatcherServlet | Spring MVC 的核心組件,是請求的入口,負責協調各個組件工做 |
HandlerMapping | 內部維護了一些 <訪問路徑, 處理器> 映射,負責爲請求找到合適的處理器 |
HandlerAdapter | 處理器的適配器。Spring 中的處理器的實現多變,好比用戶處理器能夠實現 Controller 接口,也能夠用 @RequestMapping 註解將方法做爲一個處理器等,這就致使 Spring 不止到怎麼調用用戶的處理器邏輯。因此這裏須要一個處理器適配器,由處理器適配器去調用處理器的邏輯 |
ViewResolver | 視圖解析器的用途不難理解,用於將視圖名稱解析爲視圖對象 View。 |
View | 視圖對象用於將模板渲染成 html 或其餘類型的文件。好比 InternalResourceView 可將 jsp 渲染成 html。 |
從上面的流程中能夠看出,Spring MVC 對各個組件的職責劃分的比較清晰。DispatcherServlet 負責協調,其餘組件則各自作份內之事,互不干擾。通過這樣的職責劃分,代碼會便於維護。同時對於源碼閱讀者來講,也會很友好。能夠下降理解源碼的難度,使你們可以快速理清主邏輯。這一點值得咱們學習。服務器
本章要向你們介紹一下 Servlet,爲何要介紹 Servlet 呢?緣由不難理解,Spring MVC 是基於 Servlet 實現的。因此要分析 Spring MVC,首先應追根溯源,弄懂 Servlet。Servlet 是 J2EE 規範之一,在遵照該規範的前提下,咱們可將 Web 應用部署在 Servlet 容器下。這樣作的好處是什麼呢?我以爲可以使開發者聚焦業務邏輯,而不用去關心 HTTP 協議方面的事情。好比,普通的 HTTP 請求就是一段有格式的文本,服務器須要去解析這段文本才能知道用戶請求的內容是什麼。好比我對我的網站的 80 端口抓包,而後獲取到的 HTTP 請求頭以下:mvc
若是咱們爲了寫一個 Web 應用,還要去解析 HTTP 協議相關的內容,那會增長不少工做量。有興趣的朋友能夠考慮使用 Java socket 編寫實現一個 HTTP 服務器,體驗一下解析部分 HTTP 協議的過程。也能夠參考我以前寫的文章 - 基於 Java NIO 實現簡單的 HTTP 服務器。app
若是咱們寫的 Web 應用不大,不誇張的說,項目中對 HTTP 提供支持的代碼會比業務代碼還要多,這豈不是得不償失。固然,在現實中,有現成的框架可用,並不須要本身造輪子。若是咱們基於 Servlet 規範實現 Web 應用的話,HTTP 協議的處理過程就不須要咱們參與了。這些工做交給 Servlet 容器就好了,咱們只須要關心業務邏輯怎麼實現便可。框架
下面,咱們先來看看 Servlet 接口及其實現類結構,而後再進行更進一步的說明。socket
如上圖,咱們接下來按照從上到下順序進行分析。先來看看最頂層的兩個接口是怎麼定義的。
先來看看 Servlet 接口的定義,以下:
public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
init 方法會在容器啓動時由容器調用,也可能會在 Servlet 第一次被使用時調用,調用時機取決 load-on-start 的配置。容器調用 init 方法時,會向其傳入一個 ServletConfig 參數。ServletConfig 是什麼呢?顧名思義,ServletConfig 是一個和 Servlet 配置相關的接口。舉個例子說明一下,咱們在配置 Spring MVC 的 DispatcherServlet 時,會經過 ServletConfig 將配置文件的位置告知 DispatcherServlet。好比:
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application-web.xml</param-value> </init-param> </servlet>
如上,
Servlet 中的 service 方法用於處理請求。固然,通常狀況下咱們不會直接實現 Servlet 接口,一般是經過繼承 HttpServlet 抽象類編寫業務邏輯的。Servlet 中接口很少,也不難理解,這裏就很少說了。下面咱們來看看 ServletConfig 接口定義,以下:
public interface ServletConfig { public String getServletName(); public ServletContext getServletContext(); public String getInitParameter(String name); public Enumeration<String> getInitParameterNames(); }
先來看看 getServletName 方法,該方法用於獲取 servlet 名稱,也就是
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application.xml</param-value> </context-param>
關於 ServletContext 就先說這麼多了,繼續介紹 ServletConfig 中的其餘方法。getInitParameter 方法用於獲取
以上是 Servlet 與 ServletConfig 兩個接口的說明,比較簡單。說完這兩個接口,咱們繼續往下看,接下來是 GenericServlet。
GenericServlet 實現了 Servlet 和 ServletConfig 兩個接口,爲這兩個接口中的部分方法提供了簡單的實現。好比該類實現了 Servlet 接口中的 void init(ServletConfig) 方法,並在方法體內調用了內部提供了一個無參的 init 方法,子類可覆蓋該無參 init 方法。除此以外,GenericServlet 還實現了 ServletConfig 接口中的 getInitParameter 方法,用戶可直接調用該方法獲取到配置信息。而不用先獲取 ServletConfig,而後再調用 ServletConfig 的 getInitParameter 方法獲取。下面咱們來看看 GenericServlet 部分方法的源碼:
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { // 省略部分代碼 private transient ServletConfig config; public GenericServlet() { } /** 有參 init 方法 */ public void init(ServletConfig config) throws ServletException { this.config = config; // 調用內部定義的無參 init 方法 this.init(); } /** 無參 init 方法,子類可覆蓋該方法 */ public void init() throws ServletException { } /** 未給 service 方法提供具體的實現 */ public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public void destroy() { } /** 經過 getInitParameter 可直接從 ServletConfig 實現類中獲取配置信息 */ public String getInitParameter(String name) { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getInitParameter(name); } public ServletConfig getServletConfig() { return config; } // 省略部分代碼 }
如上,GenericServlet 代碼比較簡單,配合着我寫註釋,很容易看懂。
GenericServlet 是一個協議無關的 servlet,是一個比較原始的實現,一般咱們不會直接繼承該類。通常狀況下,咱們都是繼承 GenericServlet 的子類 HttpServlet,該類是一個和 HTTP 協議相關的 Servlet。那下面咱們來看一下這個類。
HttpServlet,從名字上就可看出,這個類是和 HTTP 協議相關。該類的關注點在於怎麼處理 HTTP 請求,好比其定義了 doGet 方法處理 GET 類型的請求,定義了 doPost 方法處理 POST 類型的請求等。咱們若須要基於 Servlet 寫 Web 應用,應繼承該類,並覆蓋指定的方法。doGet 和 doPost 等方法並非處理的入口方法,因此這些方法須要由其餘方法調用才行。其餘方法是哪一個方法呢?固然是 service 方法了。下面咱們看一下這個方法的實現。以下:
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; // 調用重載方法,該重載方法接受 HttpServletRequest 和 HttpServletResponse 類型的參數 service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); // 處理 GET 請求 if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // 調用 doGet 方法 doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } // 處理 HEAD 請求 } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); // 處理 POST 請求 } else if (method.equals(METHOD_POST)) { // 調用 doPost 方法 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
如上,第一個 service 方法覆蓋父類中的抽象方法,並沒什麼太多邏輯。全部的邏輯集中在第二個 service 方法中,該方法根據請求類型分發請求。咱們能夠根據須要覆蓋指定的處理方法。
以上所述只是 Servlet 規範中的一部份內容,這些內容是和本文相關的內容。對於 Servlet 規範中的其餘內容,你們有興趣能夠本身去探索。好了,關於 Servlet 方面的內容,這裏先說這麼多。
我在前面說到,DispatcherServlet 是 Spring MVC 的核心。因此在分析這個類的源碼前,咱們有必要了解一下它的族譜,也就是繼承關係圖。以下:
如上圖,紅色框是 Servlet 中的接口和類,藍色框中則是 Spring 中的接口和類。關於 Servlet 內容前面已經說過,下面來簡單介紹一下藍色框中的接口和類,咱們從最頂層的接口開始。
● Aware
在 Spring 中,Aware 類型的接口用於向 Spring 「索要」一些框架中的信息。好比當某個 bean 實現了 ApplicationContextAware 接口時,Spring 在運行時會將當前的 ApplicationContext 實例經過接口方法 setApplicationContext 傳給該 bean。下面舉個例子說明,這裏我寫一個 SystemInfo API,經過該 API 返回一些系統信息。代碼以下:
@RestController @RequestMapping("/systeminfo") public class SystemInfo implements ApplicationContextAware, EnvironmentAware { private ApplicationContext applicationContext; private Environment environment; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println(applicationContext.getClass()); this.applicationContext = applicationContext; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @RequestMapping("/env") public String environment() { StandardServletEnvironment sse = (StandardServletEnvironment) environment; Map<String, Object> envs = sse.getSystemEnvironment(); StringBuilder sb = new StringBuilder(); sb.append("-------------------------++ System Environment ++-------------------------\n"); List<String> list = new ArrayList<>(); list.addAll(envs.keySet()); for (int i = 0; i < 5 && i < list.size(); i++) { String key = list.get(i); Object val = envs.get(key); sb.append(String.format("%s = %s\n", key, val.toString())); } Map<String, Object> props = sse.getSystemProperties(); sb.append("\n-------------------------++ System Properties ++-------------------------\n"); list.clear(); list.addAll(props.keySet()); for (int i = 0; i < 5 && i < list.size(); i++) { String key = list.get(i); Object val = props.get(key); sb.append(String.format("%s = %s\n", key, val.toString())); } return sb.toString(); } @RequestMapping("/beans") public String listBeans() { ListableBeanFactory lbf = applicationContext; String[] beanNames = lbf.getBeanDefinitionNames(); StringBuilder sb = new StringBuilder(); sb.append("-------------------------++ Bean Info ++-------------------------\n"); Arrays.stream(beanNames).forEach(beanName -> { Object bean = lbf.getBean(beanName); sb.append(String.format("beanName = %s\n", beanName)); sb.append(String.format("beanClass = %s\n\n", bean.getClass().toString())); }); return sb.toString(); } }
如上,SystemInfo 分別實現了 ApplicationContextAware 和 EnvironmentAware 接口,所以它能夠在運行時獲取到 ApplicationContext 和 Environment 實例。下面咱們調一下接口看看結果吧:
如上,咱們經過接口拿到了環境變量、配置信息以及容器中全部 bean 的數據。這說明,Spring 在運行時向 SystemInfo 中注入了 ApplicationContext 和 Environment 實例。
● EnvironmentCapable
EnvironmentCapable 僅包含一個方法定義 getEnvironment,經過該方法能夠獲取到環境變量對象。咱們能夠將 EnvironmentCapable 和 EnvironmentAware 接口配合使用,好比下面的實例:
public class EnvironmentHolder implements EnvironmentCapable, EnvironmentAware { private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public Environment getEnvironment() { return environment; } }
● HttpServletBean
HttpServletBean 是 HttpServlet 抽象類的簡單拓展。HttpServletBean 覆寫了父類中的無參 init 方法,並在該方法中將 ServletConfig 裏的配置信息設置到子類對象中,好比 DispatcherServlet。
● FrameworkServlet
FrameworkServlet 是 Spring Web 框架中的一個基礎類,該類會在初始化時建立一個容器。同時該類覆寫了 doGet、doPost 等方法,並將全部類型的請求委託給 doService 方法去處理。doService 是一個抽象方法,須要子類實現。
● DispatcherServlet
DispatcherServlet 主要的職責相信你們都比較清楚了,即協調各個組件工做。除此以外,DispatcherServlet 還有一個重要的事情要作,即初始化各類組件,好比 HandlerMapping、HandlerAdapter 等。
在第二章中,咱們知道了一個 HTTP 請求是怎麼樣被 DispatcherServlet 處理的。本節,咱們從源碼的角度對第二章的內容進行補充說明。這裏,咱們直入主題,直接分析 DispatcherServlet 中的 doDispatch 方法。這裏我把請求的處理流程圖再貼一遍,你們能夠對着流程圖閱讀源碼。
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); // 獲取可處理當前請求的處理器 Handler,對應流程圖中的步驟② mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // 獲取可執行處理器邏輯的適配器 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 (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 執行攔截器 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 調用處理器邏輯,對應步驟④ mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // 若是 controller 未返回 view 名稱,這裏生成默認的 view 名稱 applyDefaultViewName(processedRequest, mv); // 執行攔截器 preHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } // 解析並渲染視圖 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } if (mv != null && !mv.wasCleared()) { // 渲染視圖 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) {... } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } } protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; /* * 若 mv 中的 view 是 String 類型,即處理器返回的是模板名稱, * 這裏將其解析爲具體的 View 對象 */ if (mv.isReference()) { // 解析視圖,對應步驟⑤ view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } if (logger.isDebugEnabled()) {...} try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 渲染視圖,並將結果返回給用戶。對應步驟⑥和⑦ view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) {...} throw ex; } }
以上就是 doDispatch 方法的分析過程,我已經作了較爲詳細的註釋,這裏就很少說了。須要說明的是,以上只是進行了簡單分析,並無深刻分析每一個方法調用。你們如有興趣,能夠本身去分析一下 doDispatch 所調用的一些方法,好比 getHandler 和 getHandlerAdapter,這兩個方法比較簡單。從我最近所分析的源碼來看,我我的以爲處理器適配器 RequestMappingHandlerAdapter 應該是 Spring MVC 中最爲複雜的一個類。該類用於對 @RequestMapping 註解的方法進行適配。該類的邏輯我暫時沒看懂,就很少說了,十分尷尬。關於該類比較詳細的分析,你們能夠參考《看透Spring MVC》一書。
到此,本篇文章的主體內容就說完了。本篇文章從一個請求的旅行過程進行分析,並在分析的過程當中補充了 Servlet 和 DispatcherServlet 方面的知識。在最後,從源碼的角度分析了 DispatcherServlet 處理請求的過程。總的來算,算是作到了按部就班。固然,限於我的能力,以上內容可能會有一些講的很差的地方,這裏請你們見諒。同時,也但願你們多多指教。
好了,本篇文章先到這裏。謝謝你們的閱讀。
更新時間 | 標題 |
---|---|
2018-05-30 | Spring IOC 容器源碼分析系列文章導讀 |
2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
2018-06-04 | Spring IOC 容器源碼分析 - 建立單例 bean 的過程 |
2018-06-06 | Spring IOC 容器源碼分析 - 建立原始 bean 對象 |
2018-06-08 | Spring IOC 容器源碼分析 - 循環依賴的解決辦法 |
2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
2018-06-11 | Spring IOC 容器源碼分析 - 餘下的初始化工做 |
更新時間 | 標題 |
---|---|
2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
2018-06-20 | Spring AOP 源碼分析 - 建立代理對象 |
2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
更新時間 | 標題 |
---|---|
2018-06-29 | Spring MVC 原理探祕 - 一個請求的旅行過程 |
2018-06-30 | Spring MVC 原理探祕 - 容器的建立過程 |
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
做者:田小波
本文同步發佈在個人我的博客:http://www.tianxiaobo.com
本做品採用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。