前面我們學習了 Spring 最核心的 IoC 與 AOP 模塊(讀書筆記-《Spring技術內幕》(一)IoC容器的實現、讀書筆記-《Spring技術內幕》(二)AOP的實現),接下來繼續學習 MVC,其同樣也是經典。
我們依舊按照從淺到深的方式來學習,先從程序員的視角看看其簡化了哪些工作,幫我們做了什么,再到具體的設計與實現。
01
MVC 概述
在之前 IoC 的筆記中,有這樣一張圖:
這里依然可以復用,從程序員的視角來看,Spring MVC 帶來的最直觀的好處是,我們不需要再去寫繁瑣冗余的 Servlet,而是改寫 Controller。
拉長時間線來看,JavaWeb 的技術發展歷程大致如下:
總結一下就是,初期使用的技術在業務的發展中逐漸暴露出局限性,于是有了分層思想,按照數據維度分層的 MVC 是最經典的分層模式,Spring MVC 就是 MVC 的實現之一。
除了 MVC,還有按照業務維度分層的 DDD,不過后者用得比較少,其比較適合復雜系統,并且需要所有人員(產品、研發、測試)都有較高水準的業務理解。
02
Spring MVC 概述
了解了 MVC 后,我們可以很快明白 Spring MVC 的重點工作。Model 層的 Bean 初始化,Controller 層的請求處理以及 View 層的視圖呈現。具體步驟就是下面三步:
-
初始化:通過 Bean 定義,在 IoC 容器初始化時,建立起 Controller 與 HTTP 請求的映射關系。
-
處理請求:MVC 框架接收到 HTTP 請求,DispatcherServlet?根據 URL 查詢到具體的 Controller,Controller 完成請求并生成 ModelAndView 對象。
-
呈現視圖:視圖對象通過 render 方法完成視圖的呈現。
接下來我們就可以展開步驟,來詳細看看其實現了。
03
Spring MVC 設計與實現
1.初始化
我們以 Tomcat 的 web.xml 文件為例
<servlet><servlet-name>sample</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>2</load-on-startup>
</servlet><servlet-mapping><servlet-name>sample</servlet-name><url-pattern>/*</url-pattern>
</servlet-mapping><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
可以看到,其定義了一個叫 sample 的 servlet,全限定類名正是 DispatcherServlet,且其將處理所有請求。而后,這里還有一個 Bean 定義的配置文件是 WEB-IN 目錄下的 applicationContext.xml。最后,有個監聽器 ContextLoaderListener,其將負責完成 IoC 容器在 Web 環境中的啟動。
從代碼上來看,Web 容器中啟動 Spring?應用程序的過程如下:
-
ContextLoaderListener.contextInitialized() 初始化根上下文,其中調用父類?ContextLoader?的方法;
-
ContextLoader.initWebApplicationContext() 初始化 Web 應用上下文,其中有挺多異常校驗和日志打印;
-
ContextLoader.loadParentContext() 加載雙親上下文;
-
XmlWebApplicationContext.createWebApplicationContext() 創建 Web 應用上下文;
-
XmlWebApplication.refresh() 刷新。這里前面講 IoC 的時候也講到了,refresh 方法可以視作整個容器的初始化方法。
在根上下文初始化好后,就可以關注?DispatcherServlet?了。從前面的概述也看得出來,其是 Spring MVC 的核心。DispatcherServlet?的初始化和處理過程大致如下:
上面時序圖中,從右到左依次是繼承關系,我們來詳細描述下上半邊:
-
HttpServletBean.init() 基類的初始化,首先會獲取 Servlet 的初始化參數,對 Bean 屬性進行配置,也就是上面我們舉例的配置文件里的 Bean,然后調用子類的方法;
-
FrameworkServlet.initServletBean()?初始化 Servlet Bean;
-
FrameworkServlet.initWebApplicationContext()?從 ServletContext 中獲取根上下文,并設置為當前 MVC 上下文的雙親上下文,再把當前上下文設置到 ServletContext 中去(根上下文也就是上面提到的 ContextLoader 設置到 ServletContext 中去的);
-
上面的 FrameworkServlet.initWebApplicationContext() 中會調用自己的 createWebApplicationContext() ,里面就包含了 refresh();
-
DispatcherServlet.onRefresh();
-
DispatcherServlet.initStrategies() 啟動框架的初始化,代碼很簡潔,依次初始化 multipartResolver、localeResolver、themeResolver、handlerMappings、handlerAdapters、handlerExceptionResolvers、requestToViewNameTranslator、viewResolvers;
-
以 initHandlerMappings() 為例,將設置所有的 handlerMapping Bean,這些 Bean 可能在當前 DispatcherServlet 的 IoC 容器中,也可能在雙親上下文中。如果都沒有找到,則去 DispatcherServlet.properties 中找默認值。
2.處理請求
前面初始化已完成,接下來就關注上面那張圖的下半邊,也就是 DispatcherServlet 的 doDispatch() 了。
注意 HandlerMapping 有很多實現,比如通過 Bean 名稱的、通過類名稱的,我們以SimpleUrlHandlerMapping 為例,先看下關鍵的數據結構:
// 1.基類定義了方法 getHandler,返回一個 Chain
public interface HandlerMapping {HandlerExecutionChain getHandler(HttpServletRequest req) throws Exception;……
}// 2.Chain 里持有了 handler,也就是我們編寫的 Controller
// 還有個攔截器鏈,對 handler 進行功能增強
public class HandlerExecutionChain {private final Object handler;private HandlerInterceptor[] interceptors;private List<HandlerInterceptor> interceptorList;……
}// 3.關鍵的成員變量,key 是 url,value 是對應的處理 handler
// 它的賦值是在 SimpleUrlHandlerMapping.initApplicationContext() -> AbstractUrlHandlerMapping.registerHandler() 里
// 它的使用是在 AbstractHandlerMapping.getHandler() -> AbstractUrlHandlerMapping.getHandlerInternal() 里
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();……
}
再看看?doDispatch() 的詳細時序圖:
可以看到,doDispatch() 完成了模型、控制器、視圖的耦合處理,從根據請求得到對應的 handler,到調用 handler 的攔截器,到調用適配器的 handle(),最后到 ModelAndView 的呈現。
3.呈現視圖
對于最后的視圖呈現,除了當時常見的 JSP 視圖,還有 Excel 視圖、PDF 視圖等等。不過現在都不涉及了,主流的應用都通過前后端分離的方式,將數據的展示交給前端開發處理。
關于視圖呈現,我們以 JSP 視圖為例,過程如下:
-
DispatcherServlet.render()?里面有兩種情況,一種是在 ModelAndView?中設置了 View 的名稱,需要調用 resolveViewName 方法獲取 View,還有一種情況是 ModelAndView 里已經有 View 了,則直接使用(注意這里說的 render 方法是 DispatcherServlet 的,上面時序圖里說的 render 方法是 View 的);
-
DispatcherServlet.resolveViewName()?調用 ViewResolver.resolveViewName(),后者會到上下文中通過名稱把 View 的 Bean 對象獲取到;
-
AbstractView.render()?這里是基類的方法,把所有 Model 進行整合,放在一個 HashMap 里,然后繼續往下調用;
-
InternalResourceView.renderMergedOutputModel() 這里接著會往?AbstractView?調用。不過最終就是 InternalResourceView 完成了數據到頁面的輸出,以及資源的重定向處理;
-
AbstractView.exposeModelAsRequestAttributes()?把 ModelAndView 中的模型數據和請求數據都放到 HttpServletRequest 的屬性中去,這樣程序員就可以愉快的使用了。
???????
原文鏈接:讀書筆記-《Spring技術內幕》(三)MVC與Web環境
原創不易,點個關注不迷路喲,謝謝!
文章推薦:
- 如何提高核心競爭力
- 讀書筆記-《當下的力量》
- 讀書筆記-《寫給大家看的設計書》
- 賽博朋克2077玩后感
- 程序員工作中常見問題,你遇到過幾個?
- 如何設計離線跑批系統
- 讀書筆記-《人人都是產品經理》