帶你一步一步手撕Spring MVC原始碼加手繪流程圖

FrancisQ發表於2019-10-13

Servlet 與 MVC

什麼是Spring MVC 其實應該說 什麼是 MVC ?

Model 資料,View 檢視,Controller 控制器。啪!三個東西合在一起,MVC就出來了。

這麼簡單? 沒錯,其實就是這麼簡單。

當然如果你對MVC不太熟悉的話還是乖乖往下看吧。

其實MVC就是處理Web請求的一種框架模式,我們思考一下使用者請求的流程:

  1. 輸入url
  2. 傳送請求
  3. 接受響應

對於使用者來說其實也就這三個步驟,但是對於服務端來說需要做很多,這裡我畫了一張圖供大家理解。這裡其實忽略了很多 Tomcat 本身已經為我們做的,而且 Tomcat 並不僅僅只有 Host,Context。

使用者請求

我來解釋一下,使用者傳送請求的 url 其實對應著很多東西。

比如說 localhost ,當然這個就是 ip 地址。這個 ip 地址對應著 Tomcat 裡面的 Host (站點) 層。

Context 代表著一個 web 應用,還記得當初寫 Servlet 專案的時候有一個 webapp 資料夾(裡面還有個WEB-INF,最裡面是web.xml)嗎?也可以理解為當初寫的 servlet 專案就是一個 web 應用,而使用者通過 ip 地址的埠對映去找到了這個應用。

這時候我們已經通過 ip 和埠尋找到了指定的 web 應用,我們知道一個 web 應用中存在多個 servlet ,而我們如何去尋找每個請求對應的 servlet 呢? 答案還是 url ,我們通過後面的 /news 去web.xml裡面尋找已經註冊到應用中的 Servlet 類。

具體我再配合圖中解釋一下: 找到了指定的 web 應用之後,通過請求的路徑 /news 去 web.xml 中尋找是否有對應的 標籤,其中這個標籤的子標籤 標籤的值需要匹配到請求的路徑,這個時候 標籤的值為 /news 正好匹配到了,所以我們獲取了上面的標籤的值然後再尋找是否有 標籤的子標籤 和這個值相等,如果有則獲取到底下的 對應的類 並通過這個類去解析請求

總結來說就是通過 url 從 web.xml 檔案中尋找到匹配的 servlet 的類

其實這就是原生的 servlet ,那麼 MVC 的影子在哪呢?

別急,你要記住的是 MVC 就是對 Servlet 的封裝,想要理解 MVC 就必須理解 Servlet 和 MVC 與 Servlet 的關係

SpringMVC中的DispatcherServlet

DispatcherServlet的繼承結構

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

有沒有發現這個 DispatcherServlet 其實就是一個 Servlet。也就是說 Spring MVC中最核心的部分其實就是一個 Servlet 。

我來簡單解釋一下相應的部分(先簡單瞭解一下)

  • FrameworkServlet : 是 DispatcherServlet 的一個抽象父類。其中提供了載入某個對應的 web 應用程式環境的功能,還有將 GET、POST、DELETE、PUT等方法統一交給 DispatcherServlet 處理。
  • Servlet : 一個規範,用來解決 HTTP伺服器和業務程式碼之間的耦合問題
  • GenericServlet : 提升了 ServletConfig 的作用域,在init(servletConfig)方法中呼叫了init()無參方法,子類可以重寫這個無參初始化方法來做一些初始化自定義工作(後面分析原始碼中會講到)。
  • HttpServletBean : 可以將 Servlet 配置資訊作為 Bean 的屬性 自動賦值給 Servlet 的屬性。
  • DispatcherServlet :整個繼承鏈中的最後一個也是最重要的一個類,是SpringMVC 實現的核心類。MVC 通過在 web.xml 中配置 DispatcherServlet 來攔截所有請求,然後通過這個 DispatcherServlet 來進行請求的分發,並呼叫相應的處理器去處理請求和響應訊息。

有沒有想起來在 SSM 框架配置的時候在 web.xml 中的配置

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- 把所以請求都交給DispatcherServlet處理-->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!-- 攔截所有 -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

複製程式碼

好的,現在我們知道了 springMVC 中使用了一個 DispatcherServlet 去處理所有請求,而我們知道真正處理的肯定不是 DispatcherServlet ,而是具體我們在 Controller 層中寫的帶有 @Controller @RequestMapping 註解的類和底下的方法。DispatcherServlet 只是一個為我們分發請求到具體處理器的一個分發Servlet

那麼,這個 DispatcherServlet 具體怎麼工作的呢?它是如何分發請求的呢? 且聽我慢慢道來。

和 DispatcherServlet 一起工作的一些元件

首先我先將這些元件簡單化,並把一些不必要的先省略為了便於理解。

其實要分發請求處理請求並相應,我們可以肯定的是 我們需要使用一個對映關係Mapping 來表示 url 和對應的 處理器,使用一個 處理器Handler 來處理對應的請求。這樣,我們就出來了兩個最根本的角色: HandlerMappingHandler

我們再來強調一下這兩者的工作。

  • HandlerMapping : 建立請求和處理器的對映關係,即我們可以通過請求去獲取對應的 handler。
  • Handler : 處理請求的處理器。

這樣,我們就可以再畫出一個簡單的流程圖了。

簡單的處理流程

有沒有疑惑,這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的?

如果有,那麼就帶著這些問題往下看。

首先,這個 handlerMapping 的集合從哪來的?甭說集合,連單個你都不知道從哪來。 那麼我們就從原始碼中找答案吧。為了你省力,我直接告訴你,DispatcherServlet 中的 doDispatch 方法中進行了 分發的主要流程。

這裡我給出了簡化版的 doDispatch 方法

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 通過request在處理器對映HandlerMapping中獲取相應處理器
        Object handler = getHandler(req);
        if (handler != null) {
            ... 呼叫handler的處理方法
        }
    }
複製程式碼

那麼這個 getHandler(request) 方法又是什麼樣的呢?這裡我直接放 DispatcherServlet 類的原始碼

@Nullable
// 這裡返回的是 HandlerExecutionChain 
// 其實這是一個處理器和攔截器鏈的組合
// 現在你就理解為返回的是一個 handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
	        // 遍歷 handlerMapping 呼叫它的getHanlder獲取處理器
	        // 如果不為空直接返回
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}
複製程式碼

我們繼續追蹤 HandlerMapping 的getHandler(request) 方法。

其實進入原始碼你會發現,HandlerMapping 是一個介面,故這裡給出一個簡單的 HandlerMapping 介面程式碼,如果有能力可以去看原始碼。

public interface HandlerMapping {

    /**
     * 獲取請求對應的處理器
     * @param request 請求
     * @return 處理器
     * @throws Exception 異常
     */
    Object getHandler(HttpServletRequest request) throws Exception;

}
複製程式碼

那麼,具體的實現類又是什麼呢?我們思考一下,這個mapping是一個請求和處理器的對映,它是如何存的?我們當初怎麼做的?

想必,你已經有答案了,在我們使用 SSM 框架的時候我們是通過 給類和方法 配置相應的註解(@Controller,@ReqeustMapping)來建立相應的 url 和 處理器方法的對映關係的。

我們再回來看原始碼 在idea中 可以使用 ctrl+alt+B 來檢視方法實現和類實現繼承。我們檢視 HandlerMapping 介面的 getHandler 方法的實現,我們會發現直接跳到了 AbstractHandlerMapping 這個抽象類的方法,我們檢視該方法的原始碼

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 獲取 handler 這其實是一個抽象方法 
        // 子類可以通過不同實現來獲取handler
        // 例如通過 url 和 name的對映邏輯
        // 通過 requestMapping 中的 url 和對應方法的對映邏輯
	Object handler = getHandlerInternal(request);
	if (handler == null) {
	        // 如果獲取為null 的獲取預設的處理器
	        // 這裡子類也可以設定自己的預設處理器
		handler = getDefaultHandler();
	}
	// 如果還是沒有則返回 這時候 DispatcherServlet會返回 404
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	// 如果返回的處理器是字串 則認為它是一個beanName
	if (handler instanceof String) {
		String handlerName = (String) handler;
		// 通過beanName從IOC容器中獲取相應的處理器
		handler = obtainApplicationContext().getBean(handlerName);
	}
	// 下面是將處理器 和 攔截器封裝成處理器執行鏈
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	// 返回處理器執行鏈
	return executionChain;
}
複製程式碼

如果其他的你看不懂,你只要理解我註釋區域的程式碼就行了。我們從上面得到最重要的資訊就是:真正的handler獲取是在子類實現的getHandlerInternal(request)中,那我們來看一下有哪些子類。

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

我們可以看到其中有 AbstractHandlerMethodMapping、AbstractUrlHandlerMapping、WelcomeHandlerMapping。

我們主要關注 AbstractHandlerMethodMapping (提供方法處理器) 和 AbstractUrlHandlerMapping(提供url對應處理器對映),這裡為了不耽誤時間,我們直接分析 AbstractHandlerMethodMapping ,它是註解方法的對映的一個抽象類。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 獲取請求的路徑
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    this.mappingRegistry.acquireReadLock();
    try {
        // 通過 lookupPath 來從中獲取 HandlerMethod 
        // 這個HandlerMethod 又是什麼?
        // 先不用管 我們繼續看lookupHandlerMethod原始碼
    	HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    	return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
    	this.mappingRegistry.releaseReadLock();
    }
}

// 這裡的邏輯稍微有些複雜 
// 你只要知道它通過請求來匹配返回處理器方法
// 如果有多個處理器方法可以處理當前Http請求 那麼返回最佳匹配的處理器
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		matches.sort(comparator);
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		handleMatch(bestMatch.mapping, lookupPath, request);
		// 返回最佳匹配
		return bestMatch.handlerMethod;
	}
	else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}
複製程式碼

到現在邏輯慢慢變得複雜起來,我們做一個小結:在 DispatcherServlet 中我們通過遍歷 handlerMapping 集合並呼叫它的 getHandler 方法來獲取handler ,這個handler 是一個 Object (因為spring會整合其他框架的處理器,並使用這些處理器處理請求 所以這裡選擇Object)。而 HandlerMapping 僅僅是一個介面 為了方便 抽象類 AbstractHandlerMapping 實現了這個方法並且為子類提供了自定義獲取handler的 getHandlerInternal(request) 方法。 對於我們通用方式註解來標識控制器方法和url請求路徑的對映是通過 AbstractHandlerMethodMapping 來獲取請求對應的 HandlerMethod

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

那麼,疑問又來了,HandlerMethod是什麼?

還記得剛剛上面的問題麼,這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的

我們現在可以來回答一下Handler的類結構了,Handler是一個Object,為了第三方框架的處理器能夠接入來處理請求,spring使用了Object,而對於註解形式來說 一個處理器是一個 HandlerMethod。這裡我給出 HandlerMethod 的簡單實現形式,如果有能力可以檢視原始碼。

@Data
public class HandlerMethod {
    // bean 其實這個是標識 Controller 註解的類的物件
    private Object bean;
    // 該物件的型別
    private Class<?> beanType;
    // 該類上被標識RequestMapping註解的方法
    private Method method;
}
複製程式碼

在 HandlerMethod 中存放了控制器類和對應的方法。為什麼要存放他們?你想一下,我們用@RequestMapping註解標識的方法不就是處理方法嗎,HandlerMethod 中存放他們,到時候呼叫處理方法只需要通過反射呼叫這個bean的method就行了。如果不理解可以看一下我下面寫的程式碼。

// 這裡先不用管 ModelAndView
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ModelAndView modelAndView = null;
    HandlerMethod handlerMethod = ((HandlerMethod) handler);
    // 獲取HandlerMethod的method
    Method method = handlerMethod.getMethod();
    if (method != null) {
        // 通過反射呼叫方法並返回檢視物件(這就是處理方法)
        modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(handlerMethod.getBeanType()));
    }
    return modelAndView;
}
複製程式碼

再來看看上面的問題。這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的

第三個問題解決了,第二個問題上面也解決了,那麼第一個問題來了。

我們從一開始就只討論瞭如何在HandlerMapping中取出handler 並且呼叫handler的處理方法,那麼我們一開始遍歷的這個handlerMappings集合到底從哪兒來,或者說它是什麼時候被初始化的

這個時候,我們又得回到根源。我再來放這張圖,不知道你們是否還記得

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

你能想到什麼呢?我這裡假設你對servlet還是有一些瞭解的。

我們知道 DispatcherServlet 是一個 servlet 。一個 servlet 肯定有init()方法 (還記得我上面講的GenericServlet的作用嗎?現在來了,如果不是很懂init(),建議去了解一下servlet的生命週期)。

我們可以大膽的猜測,對 handlerMappings 的初始化就是在 servlet 的初始化方法中進行的。

很遺憾我們沒有能在 DispatcherServlet 中找到 init 方法,那麼就找他爹,找不到再找他爺爺,曾爺爺。我們知道因為 DispatcherServlet 繼承了 GenericServlet 所以我們需要找到 實現的 init() 無參方法。所以我們找到了 HttpServletBean 中重寫的 init() 方法了

@Override
// 這傢伙還不允許被重寫 final
public final void init() throws ServletException {

	// Set bean properties from init parameters.
	// 將servlet配置資訊存入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;
		}
	}
	// 重點在這裡 這裡子類才可以自由發揮
	// 該方法不能被重寫是因為 上面的步驟是必須的
	// 別忘了上面的步驟是 HttpServletBean 的職責
	// 接下去繼續看
	// Let subclasses do whatever initialization they like.
	initServletBean();
}

// 進入FrameworkServlet 檢視實現的initServletBean方法
@Override
protected final void initServletBean() throws ServletException {
        // log不用管
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();
	// 重點來了
	try {
	        // 初始化容器和上下文
	        // 我們要記得現在在 FrameworkServlet中執行呢
	        // 我們進入initWebApplicationContext方法
		this.webApplicationContext = initWebApplicationContext();
		// 初始化FrameworkServlet 這裡沒給實現 子類也沒給
		// 所以不用管
		initFrameworkServlet();
	}
	// log不用管
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (logger.isDebugEnabled()) {
		String value = this.enableLoggingRequestDetails ?
				"shown which may lead to unsafe logging of potentially sensitive data" :
				"masked to prevent unsafe logging of potentially sensitive data";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

	if (logger.isInfoEnabled()) {
		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
	}
}
// 初始化容器和上下文
protected WebApplicationContext initWebApplicationContext() {
        // 查詢是否有專門的根環境 先不用管
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	// 如果不存在專用根環境 通常我們不會走到這 先不用管
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 如果為空 
	if (wac == null) {
	// 檢視是否在servlet中已經註冊
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();
	}
	if (wac == null) {
	// 自己建立一個
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}
	// 判斷這個環境是否支援重新整理 如果不支援 下面手動重新整理 
	// 如果支援則前面已經重新整理了
	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		synchronized (this.onRefreshMonitor) {
		    // !!!!!!!!!!!!!!!!!!!!!重點
		    // DispacherServlet 就是在這裡實現的
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}
// DispatcherServlet 重寫了該方法
@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

// 一系列的初始化工作
protected void initStrategies(ApplicationContext context) {
        // 前面一些不用管
	initMultipartResolver(context);
	// 地域
	initLocaleResolver(context);
	// 主題
	initThemeResolver(context);
	// 重點來了!!!!
	// 初始化HandlerMapping
	initHandlerMappings(context);
	// 初始化介面卡
	initHandlerAdapters(context);
	// 初始化異常處理
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
複製程式碼

我們先暫停一下,理一下思路。

HttpServletBean 中重寫了 GenericServletinit() 無參方法開始初始化動作,其中HttpServletBean中先實現了 servlet 配置資訊到 bean 屬性資訊的賦值,然後呼叫 initServletBean() 該方法是子類進行自定義初始化的方法。FrameworkServlet 實現了該方法並且呼叫了 initWebApplicationContext() 方法進行了容器和上下文的初始化工作,並且其中呼叫了 onRefresh(ApplicationContext context) 方法。 這裡FrameworkServlet沒有做任何操作而是子類 DispatcherServlet 在其中呼叫了 initStrategies(context) 進行初始化工作。

好了我們繼續看初始化 handlerMappings方法。

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		// 在應用上下文中尋找 handlerMappings
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	// 前面不用管 其實一般我們使用預設的
	if (this.handlerMappings == null) {
	        // 這裡是獲取預設的handlerMappings
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	String key = strategyInterface.getName();
	// 獲取defaultStrategies的內容
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
	        // 解析相應內容並初始化 handlerMappings
	        // 獲取內容中的類名陣列
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<>(classNames.length);
		for (String className : classNames) {
			try {
			     //通過反射建立並加入陣列中取返回給上面
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
						"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Unresolvable class definition for DispatcherServlet's default strategy class [" +
						className + "] for interface [" + key + "]", err);
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<>();
	}
}
複製程式碼

事情馬上明瞭了,我們現在已經知道了 handlerMapping 是怎麼加入佇列中了(獲取到 defaultStrategies 的資源內容 遍歷內容獲取類名 並通過反射建立物件加入佇列),所以我們可以大膽猜測 defaultStrategies 中藏著祕密,它肯定已經定義好了預設的 handlerMapping 的類名。

果不其然,我們來看程式碼

	private static final Properties defaultStrategies;
	// 在靜態塊中已經載入了defaultStrategies
	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
		    // 通過資源初始化defaultStrategies
		    // 這裡的資源路徑 很重要!!!!
			ClassPathResource resource = new
			ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
		}
	}
	// 在這裡呀 DispatcherServlet.properties
	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
複製程式碼

我們去尋找一下 DispatcherServlet.properties 這個檔案, 原來都給我們定義好了,我們可以看見預設的handlerMapping有兩個。 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping。

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

好了,我們現在終於可以總結一下了。

在我定義的簡單的 DispatcherServlet 的 “同事”中,主要有 HandlerMapping 和 Handler。HandlerMapping 會在 DispatcherServlet 初始化的時候被在載入 ,然後在 DispatcherServlet 呼叫到執行方法 doDispatch() 的時候,會遍歷 handlerMappings 集合獲取對應的 handler。handler 是一個 Object(因為需要適配其他框架的處理器),在註解方式中是一個 HandlerMethod (裡面存了Controller類的例項和method,在處理方法的時候使用反射呼叫該例項的method方法)。獲取完 handler之後通過 處理器的處理方法返回一個檢視物件並渲染頁面。

再來一個元件 Adapter

其實對於“正宗”的MVC流程中,在遍歷 handlerMappings 獲取到相應的 handler 之後,其實並不是直接通過 handler 來執行處理方法的,而是通過 HandlerAdapter 來執行處理方法的。

這裡我寫了一個簡單的介面卡介面,原始碼也不復雜 你可以直接看原始碼

public interface HandlerAdapter {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    boolean support(Object handler);
}
複製程式碼

handleRequest 不必說,用來執行處理的,裡面傳進去一個 handler 肯定最終呼叫的 handler的執行方法。這是典型的介面卡模式。

support 判斷該handler是否被該介面卡支援。

其實理解上面的過程了之後再加入一個介面卡就不難了,我們主要思考一下 為什麼要加入介面卡,我們知道 handler 是一個 Object 它的處理方法是不固定的,如果我們要在 DispatcherServlet 中通過 Handler 執行處理方法,那麼就要做很多型別判斷,這對於 DispatcherServlet 是非常難受的,所以需要通過介面卡擴充套件。

這樣我們可以寫出一個簡單的 doDispatch 方法了,有能力的可以檢視原始碼

public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 通過request在處理器對映HandlerMapping中獲取相應處理器
    Object handler = getHandler(req);
    if (handler != null) {
        // 通過handler獲取對應的介面卡
        HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        if (handlerAdapter != null) {
            // 通過介面卡呼叫處理器的處理方法並返回ModelAndView檢視物件
            ModelAndView modelAndView = handlerAdapter.handleRequest(req, resp, handler);
            ... 處理檢視並渲染
        }
    }
}
複製程式碼

檢視解析

我們知道在 doDispatch 方法呼叫完 HandlerAdapter 的處理方法後統一返回的是一個 ModelAndView 物件,那麼這個 ModelAndView 物件是什麼呢?

字面意思,模型和檢視。也就是 MVC 的 Model 和 View。在 SpringMVC 中 ModelAndView 是給 框架本身支援的網頁生成器使用的,它是用來連線後臺和網頁的類,而如今在前後端分離的趨勢下,基本不怎麼使用了,這裡我只簡單提一下。

我們知道在 HandlerAdapter 呼叫處理方法之後會返回一個檢視物件 ModelAndView ,而在這之後,doDispatch方法會呼叫 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) 去處理檢視資訊,這個方法又會呼叫一個 render() 方法進行真正的檢視渲染。

非常有用的RequestResponseBodyMethodProcessor

還記不記得 @RequestBody @ResponseBody @RestController 這些註解。沒錯,現在我們大部分都用它們,那麼它們是如何工作的呢?

奧祕要從 doDispatch() 方法中的

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
複製程式碼

這條語句開始。 這個語句就是呼叫 相應的介面卡的 handle 方法並返回 ModelAndView 物件。

當然通過前面的學習,我們知道最終呼叫到的是 RequestMappingHandlerAdapter 類的 handleInternal方法。

// 檢視這個方法的原始碼 你會發現 除了處理一些 session 的問題
// 最終都會呼叫 處理器方法 invokeHandlerMethod
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
    	HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    ModelAndView mav;
    checkRequest(request);
    
    // Execute invokeHandlerMethod in synchronized block if required.
    // 如果配置了 session 同步
    if (this.synchronizeOnSession) {
        // 則獲取 session
    	HttpSession session = request.getSession(false);
    	if (session != null) {
    		Object mutex = WebUtils.getSessionMutex(session);
    		synchronized (mutex) {
    			mav = invokeHandlerMethod(request, response, handlerMethod);
    		}
    	}
    	else {
    		// No HttpSession available -> no mutex necessary
    		mav = invokeHandlerMethod(request, response, handlerMethod);
    	}
    }
    else {
        // 如果沒有配置 session 內同步 或者還沒有建立 session 直接呼叫處理器方法
    	// No synchronization on session demanded at all...
    	mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    	if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    		applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    	}
    	else {
    		prepareResponse(response);
    	}
    }

	return mav;
}
複製程式碼

我們來看一下 invokeHandlerMethod 中幹了什麼事, 看上去好密密麻麻,其實我們只要關注重點就行了,關注我註釋的地方。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	// 構造 Web 請求 其實就是一個代理類 封裝了 請求和響應
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		// 重點來了!!!!!
		// 將handlerMethod 封裝成 ServletInvocableHandlerMethod類
		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		// 為invocableMethod 做一些配置
		invocableMethod.setDataBinderFactory(binderFactory);
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.setTaskExecutor(this.taskExecutor);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		asyncManager.registerCallableInterceptors(this.callableInterceptors);
		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

		if (asyncManager.hasConcurrentResult()) {
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		// 重點來了!!!!! 呼叫 ServletInvocableHandlerMethod 的 invokeAndHandle 方法
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}
複製程式碼

總結一下上面的方法就是:將 HandlerMethod 物件封裝成 ServletInvocableHandlerMethod 然後做一些配置並呼叫它的 invokeAndHandle 方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 這一步很重要 執行請求並獲取返回值
	// 這裡裡面就涉及到了 RequestBody 註解了
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);
	// 處理返回值
	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}
複製程式碼

我們首先來解析一下 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs) 方法 ,這裡涉及到了 @RequestBody 註解的使用。

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 方法引數的解析 這裡可以做很多關於 引數和請求 的事情
	// 這是我們需要深入檢視原始碼的
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	// 返回撥用結果 很簡單 就是通過反射呼叫方法 這裡不做贅述
	return doInvoke(args);
}

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	// 很簡單 獲取到引數
	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return EMPTY_ARGS;
	}

	Object[] args = new Object[parameters.length];
	// 遍歷引數
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		args[i] = findProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}
		if (!this.resolvers.supportsParameter(parameter)) {
			throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
		}
		try {
		    // 重點來了!!!!
		    // 將請求中的資訊通過引數解析器解析到對應的引數
		    // 最終遍歷完之後將引數陣列返回
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
		catch (Exception ex) {
			// Leave stack trace for later, exception may actually be resolved and handled...
			if (logger.isDebugEnabled()) {
				String exMsg = ex.getMessage();
				if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
					logger.debug(formatArgumentError(parameter, exMsg));
				}
			}
			throw ex;
		}
	}
	return args;
}

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// 獲取對應的引數解析器
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	if (resolver == null) {
		throw new IllegalArgumentException("Unsupported parameter type [" +
				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
	}
	// 通過解析器解析引數 重點就在這了 因為 @RequestBody註解的存在
	// 我們會呼叫到 RequestResponseBodyProcessor 類的這個方法
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
	// 主要這裡面通過 MessageConverters 訊息轉換器 來實現了 @RequestBody 的功能
	// 由於篇幅有限 這裡不再深入分析 如果想找到答案 順著往下檢視原始碼就行
	Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
	String name = Conventions.getVariableNameForParameter(parameter);

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		if (mavContainer != null) {
			mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}

	return adaptArgumentIfNecessary(arg, parameter);
}
複製程式碼

下面我還放了一下 readWithMessageConverters 方法的程式碼,其實裡面主要就是遍歷訊息轉換器,然後通過轉換器執行HTTP報文到引數的轉換。

for (HttpMessageConverter<?> converter : this.messageConverters) {
	Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
	GenericHttpMessageConverter<?> genericConverter =
			(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
	if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
			(targetClass != null && converter.canRead(targetClass, contentType))) {
		if (message.hasBody()) {
			HttpInputMessage msgToUse =
					getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
			body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
					((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
			body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
		}
		else {
			body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
		}
		break;
	}
}
複製程式碼

知道了 @RequestBody 註解的原理,@ResponseBody 註解的原理也馬上浮出水面了。答案就在 ServletInvocableHandlerMethod 類中的 invokeAndHandle 方法獲取了 returnValue 之後的步驟

// 答案就在這裡
try {
	this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
複製程式碼

上面兩個方法你可以追蹤原始碼,其實最終呼叫的還是在 RequestResponseBodyMethodProcessor 這個類中。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	mavContainer.setRequestHandled(true);
	// 封裝web請求
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
	// 通過訊息解析器解析返回的value
	// Try even with null return value. ResponseBodyAdvice could get involved.
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

// 這裡我貼出了writeWithMessageConverters方法的主要程式碼 因為這個方法有點長。。
// 這裡遍歷 Http訊息轉換器集合
for (HttpMessageConverter<?> converter : this.messageConverters) {
	GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
			(GenericHttpMessageConverter<?>) converter : null);
	if (genericConverter != null ?
			((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
			converter.canWrite(valueType, selectedMediaType)) {
		body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
				(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
				inputMessage, outputMessage);
		if (body != null) {
			Object theBody = body;
			LogFormatUtils.traceDebug(logger, traceOn ->
					"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
			addContentDispositionHeader(inputMessage, outputMessage);
			// 通過轉換器來輸出  重點。。。
			if (genericConverter != null) {
				genericConverter.write(body, targetType, selectedMediaType, outputMessage);
			}
			else {
				((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Nothing to write: null body");
			}
		}
		return;
	}
}
複製程式碼

我們來總結一下: 在呼叫 HandlerAdapter 的 處理方法的時候 會跳轉呼叫到 RequestMappingHandlerAdapter 的 handleInternal 方法。這裡面會將 原本的處理器 HandlerMethod 封裝成 ServletInvocableHandlerMethod,然後會呼叫這個類中的 invokeAndHandle 方法,這個方法中主要進行了相應方法處理器的方法的呼叫,在呼叫之前,會將Http報文中的內容轉換為對應的引數內容。在呼叫完成返回 returnValue 之後,會呼叫相應 HttpMessageConvert 的轉換方法 然後返回。

最終變成什麼樣了呢?

帶你一步一步手撕Spring MVC原始碼加手繪流程圖

現在,你理解 Spring MVC了麼?

相關文章