DispatcherServlet類源碼分析

来源:https://www.cnblogs.com/bkycnd/archive/2022/09/04/16654132.html
-Advertisement-
Play Games

我的另一篇博文中提到JavaScript 有哪些是假值,哪些是真值。對於 null、undefined、"",等一些假值,JavaScript 直接視為 false。 我有一個需求,判斷從瀏覽器中獲取的 Cookie 是否存在,如果存在我就返回 true,否則返回 false。useCookies( ...


DispatcherServlet類結構圖

DispatcherServlet類結構圖

 

DispatcherServlet源碼分析

1. 載入配置文件

/** 
 * This implementation calls {@link #initStrategies}. 
 */  
@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()方法我們可以看出DispatcherServlet實例化時會初始化web層相關的bean,如HandlerMapping,HandlerAdapter等,並且如果我們沒有進行配置,DispatcherServlet會提供預設的配置。以上的Servlet的體繫結構以及DispatcherServlet的實例化過程我們可以看出主要完成以下幾個事情:

 

(1)通過配置Servlet實現SpringMVC核心控制器DispatcherServlet的初始化;

 

(2)通過ServletContext共用Spring根上下文,使得每一個Servlet實例獲取根上下文中的bean,用於實例化SpringMVC web層的相關bean。

(3)初始化DispatcherServlet作為核心控制器,接收處理請求需要的相關資源,如HandlerMapping,HandlerAdapter等。

(4)通過Servlet體繫結構中的繼承關係以及抽象方法,可以根據具體的需求對各個層級的Servlet抽象方法進行重寫以滿足不同的功能需要,父類中只定義流程和方法引用,具體實現由子Servlet完成,實現定義與實現的分離,便於擴展。

2. processRequest()方法

@Override  
protected final void doGet(HttpServletRequest request, HttpServletResponse response)  
        throws ServletException, IOException {  
    processRequest(request, response);  
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)  
        throws ServletException, IOException {  
  
    long startTime = System.currentTimeMillis();  
    Throwable failureCause = null;  
  
    // Expose current LocaleResolver and request as LocaleContext.  
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();  
    LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);  
  
    // Expose current RequestAttributes to current thread.  
    RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();  
    ServletRequestAttributes requestAttributes = null;  
    if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {  
        requestAttributes = new ServletRequestAttributes(request);  
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);  
    }  
  
    if (logger.isTraceEnabled()) {  
        logger.trace("Bound request context to thread: " + request);  
    }  
  
    try {  
        doService(request, response);  
    }  
    catch (ServletException ex) {  
        failureCause = ex;  
        throw ex;  
    }  
    catch (IOException ex) {  
        failureCause = ex;  
        throw ex;  
    }  
    catch (Throwable ex) {  
        failureCause = ex;  
        throw new NestedServletException("Request processing failed", ex);  
    }  
  
    finally {  
        // Clear request attributes and reset thread-bound context.  
        LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);  
        if (requestAttributes != null) {  
            RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);  
            requestAttributes.requestCompleted();  
        }  
        if (logger.isTraceEnabled()) {  
            logger.trace("Cleared thread-bound request context: " + request);  
        }  
  
        if (logger.isDebugEnabled()) {  
            if (failureCause != null) {  
                this.logger.debug("Could not complete request", failureCause);  
            }  
            else {  
                this.logger.debug("Successfully completed request");  
            }  
        }  
        if (this.publishEvents) {  
            // Whether or not we succeeded, publish an event.  
            long processingTime = System.currentTimeMillis() - startTime;  
            this.webApplicationContext.publishEvent(  
                    new ServletRequestHandledEvent(this,  
                            request.getRequestURI(), request.getRemoteAddr(),  
                            request.getMethod(), getServletConfig().getServletName(),  
                            WebUtils.getSessionId(request), getUsernameForRequest(request),  
                            processingTime, failureCause));  
        }  
    }  
}

DispatcherServlet也是通過自己的service()方法來接收和轉發Http請求到具體的doGet()或doPost()這些方法的。以一次典型的GET請求為例,經過HttpServlet基類中service()方法的委派,請求會被轉發到doGet()方法中。doGet()方法,在DispatcherServlet的父類FrameworkServlet類中被覆寫。

 

processRequest()方法理解的要點是以doService()方法為區隔,前一部分是將當前請求的Locale對象和屬性,分別設置到LocaleContextHolder和RequestContextHolder這兩個抽象類中的ThreadLocal對象中,也就是分別將這兩個東西和請求線程做了綁定。在doService()處理結束後,再恢復回請求前的LocaleContextHolder和RequestContextHolder,也即解除線程綁定。每次請求處理結束後,容器上下文都發佈了一個ServletRequestHandledEvent事件,你可以註冊監聽器來監聽該事件。
可以看到,processRequest()方法只是做了一些線程安全的隔離,真正的請求處理,發生在doService()方法中。

3. doService()方法

@Override  
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
	if (logger.isDebugEnabled()) {  
		String requestUri = urlPathHelper.getRequestUri(request);  
		logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +  
				" request for [" + requestUri + "]");  
	}  

	// Keep a snapshot of the request attributes in case of an include,  
	// to be able to restore the original attributes after the include.  
	Map<string, object=""> attributesSnapshot = null;  
	if (WebUtils.isIncludeRequest(request)) {  
		logger.debug("Taking snapshot of request attributes before include");  
		attributesSnapshot = new HashMap<string, object="">();  
		Enumeration attrNames = request.getAttributeNames();  
		while (attrNames.hasMoreElements()) {  
			String attrName = (String) attrNames.nextElement();  
			if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {  
				attributesSnapshot.put(attrName, request.getAttribute(attrName));  
			}  
		}  
	}  

	// Make framework objects available to handlers and view objects.  
	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());  

	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); //這邊最終也是調用了doDispatch方法,該方法主要用來處理SPring框架的具體業務分發邏輯。  
	}  
	finally {  
		// Restore the original attribute snapshot, in case of an include.  
		if (attributesSnapshot != null) {  
			restoreAttributesAfterInclude(request, attributesSnapshot);  
		}  
	}  
}

doService()方法中requet.setAttribute()方法的調用,將前面在初始化流程中實例化的對象設置到http請求的屬性中,供下一步處理使用,其中有容器的上下文對象、本地化解析器等SpringMVC特有的編程元素。不同於Struts2中的ValueStack,SpringMVC的數據並沒有從HttpServletRequest對象中抽離出來再存進另外一個編程元素,這也跟SpringMVC的設計思想有關。因為從一開始,SpringMVC的設計者就認為,不應該將請求處理過程和Web容器完全隔離。所以,真正發生請求轉發的方法doDispatch()中,它的參數是HttpServletRequest和HttpServletResponse對象。

4. doDispatch()方法

//Spring框架最終的分發都是通過該方法的  
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
	HttpServletRequest processedRequest = request;  
	HandlerExecutionChain mappedHandler = null;  
	int interceptorIndex = -1;  

	try {  
		ModelAndView mv;  
		boolean errorView = false;  

		try {  
			processedRequest = checkMultipart(request);  

			// Determine handler for the current request.  
			mappedHandler = getHandler(processedRequest, false);  
			if (mappedHandler == null || mappedHandler.getHandler() == null) {  
				noHandlerFound(processedRequest, response);  
				return;  
			}  

			// Determine handler adapter for the current request.  
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  

			// Process last-modified header, if supported by the handler.  
			String method = request.getMethod();  
			boolean isGet = "GET".equals(method);  
			if (isGet || "HEAD".equals(method)) {  
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  
				if (logger.isDebugEnabled()) {  
					String requestUri = urlPathHelper.getRequestUri(request);  
					logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);  
				}  
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  
					return;  
				}  
			}  

			// 這裡是處理前置攔截器  
			HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();  
			if (interceptors != null) {  
				for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } //處理最終的Action邏輯 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } //處理後置攔截器 if (interceptors != null) { for (int i = interceptors.length - 1; i >= 0; i--) {  
					HandlerInterceptor interceptor = interceptors[i];  
					interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);  
				}  
			}  
		}  
		catch (ModelAndViewDefiningException ex) {  
			logger.debug("ModelAndViewDefiningException encountered", ex);  
			mv = ex.getModelAndView();  
		}  
		catch (Exception ex) {  
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
			mv = processHandlerException(processedRequest, response, handler, ex);  
			errorView = (mv != null);  
		}  

		// Did the handler return a view to render?  
		if (mv != null && !mv.wasCleared()) {  
			render(mv, processedRequest, response);  
			if (errorView) {  
				WebUtils.clearErrorRequestAttributes(request);  
			}  
		}  
		else {  
			if (logger.isDebugEnabled()) {  
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +  
						"': assuming HandlerAdapter completed request handling");  
			}  
		}  

		// Trigger after-completion for successful outcome.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  
	}  

	catch (Exception ex) {  
		// Trigger after-completion for thrown exception.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
		throw ex;  
	}  
	catch (Error err) {  
		ServletException ex = new NestedServletException("Handler processing failed", err);  
		// Trigger after-completion for thrown exception.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
		throw ex;  
	}  

	finally {  
		// Clean up any resources used by a multipart request.  
		if (processedRequest != request) {  
			cleanupMultipart(processedRequest);  
		}  
	}  
}

doDispatch()是整個請求轉發流程中最核心的方法,DispatcherServlet所接收的Http請求,經過層層轉發,最終都是彙總到這個方法中來進行最後的請求分發和處理。它通過高度抽象的介面,描述出了一個MVC(Model-View-Controller)設計模式的實現方案。Model、View、Controller三種層次的編程元素,在SpringMVC中都有大量的實現類,各種處理細節也是千差萬別。但是,它們最後都是由,也都能由doDispatch()方法來統一描述,這就是介面和抽象的威力,萬變不離其宗。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 【突然想多瞭解一點】可以用 Task.Run() 將同步方法包裝為非同步方法嗎? 本文翻譯自《Should I expose asynchronous wrappers for synchronous methods? - Stephen Toub》,原文地址:Should I expose asyn ...
  • 此篇文章演示基本的基於docker部署.netcore服務,linux系統騰訊雲ubuntu,.net core版本3.1。 1.安裝docker apt install docker.io 2.拉取.net core依賴鏡像 docker pull mcr.microsoft.com/dotnet ...
  • 目錄 HC32L110(一) HC32L110晶元介紹和Win10下的燒錄 HC32L110(二) HC32L110在Ubuntu下的燒錄 HC32L110(三) HC32L110的GCC工具鏈和VSCode開發環境 HC32L110(四) HC32L110的startup啟動文件和ld連接腳本 H ...
  • 首先先來瞭解一下TMC5160的3種工作模式 TMC5160通過兩個引腳來控制它的工作模式:SD_MODE和SPI_MODE。 1、當SD_MODE接地,SPI_MODE拉高,TMC5160即工作在模式1(SPI控制模式)。在該模式下,用戶通過SPI介面來設置TMC5160的寄存器。 TMC5160 ...
  • 觀前提示 此處假定你已經安裝好Windows,並且能夠看懂英語。 下載Ubuntu 這裡 不同於一般推薦於英文官網下載,此處建議在中文官網下載。這樣可以利用Ubuntu在國內架設的鏡像,也方便日後使用apt-get等工具安裝軟體。(實際作者並不清楚安裝時是否會自動測試鏡像列表) 即使使用國內官方鏡像 ...
  • 以下介紹項目中的startup和ld文件, 以及HC32L110的啟動機制, 因為是面向 GCC Arm Embedded 工具鏈的版本, 所以 startup 代碼和 ld 連接描述腳本都依據 GCC Arm 工具鏈的格式. ...
  • ClickHouse屬於分析型資料庫,ClickHouse提供了許多數據類型,它們可以劃分為基礎類型、複合類型和特殊類型。其中基礎類型使ClickHouse具備了描述數據的基本能力,而另外兩種類型則使ClickHouse的數據表達能力更加豐富立體。 基礎類型 基礎類型只有數值、字元串和時間三種類型, ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 一. XDP Socket示例解析 源碼參見:https://github.com/xdp-project/xdp-tutorial/tree/mast ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...