1. 程式人生 > 程式設計 >帶你一步一步手撕Spring MVC原始碼加手繪流程圖

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

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的繼承結構

有沒有發現這個 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)中,那我們來看一下有哪些子類。

我們可以看到其中有 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(),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(),request);
	}
}
複製程式碼

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

那麼,疑問又來了,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集合到底從哪兒來,或者說它是什麼時候被初始化的

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

你能想到什麼呢?我這裡假設你對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。

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

在我定義的簡單的 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,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,mappedHandler.getHandler());
複製程式碼

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

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

// 檢視這個方法的原始碼 你會發現 除了處理一些 session 的問題
// 最終都會呼叫 處理器方法 invokeHandlerMethod
@Override
protected ModelAndView handleInternal(HttpServletRequest request,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,handlerMethod);
    		}
    	}
    	else {
    		// No HttpSession available -> no mutex necessary
    		mav = invokeHandlerMethod(request,handlerMethod);
    	}
    }
    else {
        // 如果沒有配置 session 內同步 或者還沒有建立 session 直接呼叫處理器方法
    	// No synchronization on session demanded at all...
    	mav = invokeHandlerMethod(request,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,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,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),webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue),ex);
		}
		throw ex;
	}
}
複製程式碼

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

@Nullable
public Object invokeForRequest(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
	// 方法引數的解析 這裡可以做很多關於 引數和請求 的事情
	// 這是我們需要深入檢視原始碼的
	Object[] args = getMethodArgumentValues(request,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,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,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,webRequest,binderFactory);
}

@Override
public Object resolveArgument(MethodParameter parameter,@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,targetType,converterType);
			body = (genericConverter != null ? genericConverter.read(targetType,msgToUse) :
					((HttpMessageConverter<T>) converter).read(targetClass,msgToUse));
			body = getAdvice().afterBodyRead(body,msgToUse,converterType);
		}
		else {
			body = getAdvice().handleEmptyBody(null,message,converterType);
		}
		break;
	}
}
複製程式碼

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

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

@Override
public void handleReturnValue(@Nullable Object returnValue,MethodParameter returnType,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,webRequest);
}
複製程式碼

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

@Override
public void handleReturnValue(@Nullable Object returnValue,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,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,selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),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,outputMessage);
			}
			else {
				((HttpMessageConverter) converter).write(body,outputMessage);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Nothing to write: null body");
			}
		}
		return;
	}
}
複製程式碼

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

最終變成什麼樣了呢?

現在,你理解 Spring MVC了麼?