Spring原始碼學習【八】SpringMVC之DispatcherServlet
目錄
一、前言
Web環境是Spring框架的重要應用場景,而SpringMVC又是Web開發中一個常用的框架,因此我們有必要學習一下SpringMVC的實現原理。
回到Web專案的配置檔案web.xml中,在使用SpringMVC時我們需要進行如下的配置:
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/springMVC.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
熟悉Spring的同學對以上的配置肯定不陌生,這裡配置了一個DispatcherServlet,這個Servlet是由Spring實現的,是SpringMVC最核心的部分,如上配置的這個Servlet會接收所有的請求,最終將請求分發至對應的Controller進行處理,下面我們就從DsipatcherServlet入手,學習SpringMVC的實現。
二、原始碼學習
首先,來看一看DsipatcherServlet的類繼承關係(省略了部分介面):
從上圖中可以看到,DispatcherServlet間接繼承了HttpServlet,可用於處理Http請求。
既然DispatcherServlet也是Servlet家族中的一員,那麼它肯定要遵循Servlet的生命週期,即:
- 初始化階段,呼叫init()方法
- 響應客戶請求階段,呼叫service()方法
- 銷燬階段,呼叫destroy()方法
有了這些瞭解,我們就可以順著DispatcherServlet的生命週期來學習SpringMVC的實現了。
(一)初始化階段 -> init()
首先,定位到初始化階段,在這個階段會呼叫init()方法,這個方法定義在Serlvet介面中,我們可以發現這個方法的最終實現在DispatcherServlet的父類HttpServletBean中,這個方法被定義為final方法,不可被子類覆蓋,程式碼如下:
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // 獲取配置的 init parameters,設定Bean的屬性 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { 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) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // 這個方法是一個模板方法,預設實現為空,交由其子類FrameworkServlet實現 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } }
在上面的程式碼中,首先獲取了init parameters,也就是web.xml中的<init-param/>節點,並將init parameters設定為這個Servlet Bean的屬性,然後呼叫了子類FrameworkServlet的initServletBean()方法,進行額外的初始化處理,FrameworkServlet程式碼如下:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 初始化應用上下文
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
// 首先從ServletContext中取得根應用上下文,也就是上一篇中在ContextLoader中建立的IOC容器
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 若在構造Servlet時已經注入應用上下文,則直接使用
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 設定根應用上下文
cwac.setParent(rootContext);
}
// 配置並重新整理應用上下文
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 構造Servlet時未注入應用上下文,則到ServletContext中獲取
wac = findWebApplicationContext();
}
if (wac == null) {
// ServletContext中未獲取到,則建立一個應用上下文
// 這裡建立應用上下文的處理與上一篇中的處理類似,不同之處在於建立完成後即進行了應用上下文的配置和重新整理
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 觸發初始化重新整理,這裡指的不是應用上下文的重新整理
// 這個方法是一個模板方法,預設實現為空,交由子類DisispatcherServlet實現
onRefresh(wac);
}
if (this.publishContext) {
// 將當前的應用上下文釋出到ServletContext中,key為:FrameworkServlet.class.getName() + ".CONTEXT." + servletName
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;
}
}
上面的程式碼中,取得了一個應用上下文,作為了根IOC容器的子容器,這樣,DispatcherServlet中的IOC容器就建立起來了,細心的同學會發現,在返回應用上下文之前呼叫了onRefresh(wac)方法,這個方法由其子類DispatcherServlet實現,用於初始化Web層需要的策略,下面讓我們一起來看一看這部分的原始碼:
public class DispatcherServlet extends FrameworkServlet {
@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);
}
}
從上面的程式碼中可以看到,在獲取應用上下文的過程中初始化了DispatcherServlet中需要的各種解析器,其中包括檔案解析器、區域解析器、主題解析器等。
解析器的初始化過程大體相同,都是從應用上下文中取得相應的Bean,若不存在則使用預設解析器策略。
具體關於各解析器的介紹大家可以參考一篇部落格:SpringMVC解析器
到這裡,DispatcherServlet的初始化階段就完成了,在這個過程中,一方面建立了DispatcherServlet的IOC容器,並將這個IOC容器作為根IOC容器的子容器,另一方面,初始化了DispatcherServlet需要的各種解析策略,接下來,DispatcherServlet將會在處理HTTP請求時發揮重要的作用。
(二)響應客戶請求 -> service()
我們知道Servlet在接收到客戶請求後會呼叫service()方法,根據請求型別執行doGet、doPost等一系列方法,在DispatcherServlet的繼承體系中,由DispatcherServlet的父類FrameworkServlet重寫了HttpServlet中的service()方法以及doGet()、doPost() 等一系列方法,下面以常用的HTTP請求方法來看一看FrameworkServlet的主要實現程式碼:
-
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); // 這裡添加了對patch請求的支援 if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } else { // 這裡呼叫HttpServlet的service方法,根據請求型別呼叫該類中重寫的doGet、doPost等方法 super.service(request, response); } } @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); } @Override protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } }
從上面的程式碼中可以看到, DispatcherServlet接收到使用者請求後,會呼叫父類FrameworkServlet中的service()方法,最終根據請求型別呼叫FrameworkServlet中重寫的doGet、doPost等方法,這些方法都呼叫了processRequest()方法,下面讓我們看一下processRequest()的具體實現:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 獲取LocaleContext(語言環境), 用作備份
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 根據當前request建立LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
// 獲取RequestAttributes,用作備份
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 根據當前request、response建立ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 為當前請求註冊一個攔截器,用於在請求執行前後非同步初始化和重置FrameworkServlet的LocaleContextHolder和RequestContextHolder
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 根據當前request初始化ContextHolders
initContextHolders(request, localeContext, requestAttributes);
try {
// 具體處理請求,是一個模板方法,由子類DispatcherServlet實現
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 使用備份重置ContextHolders
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
// 向該應用註冊的所有監聽器釋出RequestHandledEvent事件
// 監聽器可以通過實現ApplicationListener介面來實現
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
}
在上面的程式碼中我們可以看到一個用於處理請求的核心方法:doService(request, response),這個方法是一個模板方法,由其子類DispatcherServlet實現,程式碼如下:
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// 對於include請求,首先儲存request屬性快照,用於請求後恢復屬性
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 為request設定一些必要的屬性
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); // 應用上下文
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 語言環境解析器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 主題解析器
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 主題源,預設將應用上下文作為主題源
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 分發請求
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
// 恢復include請求屬性
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
}
doService方法比較簡單,主要是為request設定一些必要的屬性,接下來呼叫了doDispatch方法進行請求的分發,這是SpringMVC中的核心功能,doDispatch方法中主要進行了如下的處理:
- 處理攔截
- 處理請求
- 解析View
程式碼如下:
public class DispatcherServlet extends FrameworkServlet {
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 {
// 判斷是否為檔案上傳請求,這裡會嘗試將request轉換為檔案上傳請求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 通過HandlerMapping獲取請求匹配的處理器:一個HandlerExecutionChain類的例項
// 這個處理器中包含一個請求處理器和多個攔截器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 未找到匹配的處理器,返回404錯誤
noHandlerFound(processedRequest, response);
return;
}
// 根據匹配的請求處理器獲取支援該處理器的介面卡
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
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);
}
// 對於get請求,如果從上次修改後未進行修改則不再對請求進行處理
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 執行攔截器 -> preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 執行處理器處理請求,返回ModelAndView物件
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 若ModelAndView無檢視名,則為其設定預設檢視名
applyDefaultViewName(processedRequest, mv);
// 執行攔截器 -> postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 處理異常,解析View
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 觸發攔截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 觸發攔截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 清除檔案上傳請求使用的資源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
}
到這裡,客戶的請求就相應完成了。在這個過程中,首先處理匹配的處理器中的攔截器,然後通過處理器處理客戶的請求,最後通過檢視解析器解析和渲染檢視,這裡還有許多細節未深入分析,我們將在後續繼續學習。
(二)銷燬階段 -> destroy()
Servlet的銷燬階段會呼叫destroy()方法,這個方法的實現在FrameworkServlet中,實現比較簡單,就是將Servlet中的IOC容器關閉,程式碼如下:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
@Override
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
// Only call close() on WebApplicationContext if locally managed...
if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
}
三、總結
本篇中,順著Servlet的生命週期大致分析了SpringMVC的核心類DispatcherServlet的實現,對SpringMVC的請求控制有了一定的瞭解,但在DispatcherServlet處理客戶請求的部分有許多內容未深入分析,需要進一步學習。