深入理解SpringMvc 啟動流程


徹底搞懂 HandlerMapping和HandlerAdapter

知識點的回顧:

當Tomcat接收到請求后會回調Servlet的service方法,一開始入門Servlet時,我們會讓自己的Servlet去實現HttpServlet接口,重寫它的doGet()doPost()方法

DispatcherServlet繼承 體系
在SpringMvc中,SpringMvc的核心組件DispatcherSerlvet的繼承圖如上,可以看到上圖,其實這個DispatcherServlet終究還是一個Servlet

我們追蹤一下他的生命周期創建過程, 首先是說Servlet的創建時機,其實是存在兩種情況的, 這取決於.setLoadOnStartup(1);設置的啟動級別,當然一般都會設置成正數,表示當容器啟動時實例化Servlet

於是Tomcat實例化Servlet,Servlet被初始化時首先被回調的方法是init()這大家都知道的,但是SpringMvc提供的DispatcherServlet中存在一個靜態塊,源碼如下: 這個靜態塊干了什么事呢? 讀取的是class path 下面的 DispatcherServlet.properties

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			// todo 它讀取的是class path 下面的 DispatcherServlet.properties 配置文件
			// todo resource/web/servlet/DispatcherServlet.properties
			// todo 將這些默認的實現信息,封裝進了Properties  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如此迫切的去加載? 我們文件貼在下面,可以看到存放的是一些全類名,這些是DiapacherServlet針對不同策略接口提供的八個默認的實現,當在上下文中沒有匹配到程序員添加的這些實現時,就會使用這些默認的實現

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
# todo 這里存在 DiapacherServlet策略接口的八個默認的實現
# todo 當在上下文中沒有匹配到程序員添加的這些實現時,就會使用這些默認的實現
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver



org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

好吧, 雖然這也不算跑題,但是我們還是回到DispatcherServletinit()方法,其實去這個DispatcherServlet中是找不到這個init()方法的, 那這個方法在哪里了呢? 其實就在他的祖父類HttpServletBean中,源碼如下:

意圖很明確,前面用來初始化環境參數,后者調用 initServletBean();

@Override
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}

	// Set bean properties from init parameters.
	// todo 設置初始化參數
	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;
		}
	}

	// Let subclasses do whatever initialization they like.
	// todo 初始化SerlvetBean
	initServletBean();

	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

initServletBean(); 見名知意,初始化ServletBean,說白了就是想去初始化DispatcherServlet唄,跟進去查看,不出意料,他是個抽象方法,跟進它的實現類

他的實現類是FrameworkServlet,進去跟進,看他去創建應用的上下文,但是如果上下文已經被初始化了,他是不會重復創建上下文的

我們繼續跟進它的onRefresh()方法,同樣這個方法是一個抽象方法,而它是實現就是我們關注的DispatcherServlet,源碼如下: 在下面做了很多事,我們還是僅僅關注兩點,初始化了handlerMappinghandlerAdapater

protected void onRefresh(ApplicationContext context) {
		// todo 進行跟進,方法就在下面
		initStrategies(context);
	}
	
protected void initStrategies(ApplicationContext context) {
// todo 初始化和文件上傳相關的解析器
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);

/**
 *  todo 初始化處理器映射器,這是我們看到重點
 *  todo 什么是處理器映射器?
 *  todo Controller在Spring中有多種情況, 那當一個用戶的請求到來時, 如何進一步找到哪一種Controller來處理用戶的請求呢?
 *  todo 這一步就是通過處理器映射器完成的
 *  todo  說白了, 通過處理器映射器我們可以找到那個處理當前請求的特定的組件, 我們稱它為handler
 *  todo 但是這之間存在一個問題,就是說,這個handler到底是方法級別的,還是類級別的我們是不知道的,只能說,這個handler就肯定能處理這次請求而已
 */
initHandlerMappings(context);

initHandlerAdapters(context);


initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

到現在為止,本位關注的兩個組件其實就完成了初始化了, 下一個問題就來了,什么時候使用他們呢?

那就得從Servlet的service()方法說起了,大家都知道這個方法會在Serlvet每一次收到請求時就會被回調一次,再回想我們原來是怎么變成來着? 但是后來我們都直接實現HttpServlet接口,然后重寫他們的doGet()doPost()來實現我們自己的Servlet, 那SpringMvc是怎么做的呢?

源碼如下:

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}

都是通過processRequest(request, response);來實現的,我們往下追蹤這個方法,最終也是不出所料,我們來到了DispacherServlet

我截取部分源碼如下:

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 {
		//檢查請求中是否存在文件上傳的操作
		processedRequest = checkMultipart(request);
		multipartRequestParsed = (processedRequest != request);

		// Determine handler for the current request.
		//todo 確定當前請求的處理程序,跟進去
		//todo 換句話說就是 推斷Controller的類型, Controller存在三種類型
		// todo 跟進去看看這個handlerMapping的獲取方式
		mappedHandler = getHandler(processedRequest);
		if (mappedHandler == null) {
			noHandlerFound(processedRequest, response);
			return;
		}

		// Determine handler adapter for the current request.
		// todo 用到了適配器設計模式,
		// todo 如果 上面的HandlerExecutionChain 是bean類型, 經過這個方法后將被設置成bean
		// todo 如果 上面的HandlerExecutionChain 是 method 類型, 經過這個方法后將被設置成嗎method
		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()) {
				logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
			}
			if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
				return;
			}

同樣的,我們只是關注上面的兩點,看看SpringMvc是如何玩轉HandlerMappingHandlerAdapter的, 分兩步,先跟進 mappedHandler = getHandler(processedRequest);方法

看看他干啥了,遍歷所有的HandlerMapping

第一個問題: 還記不記得哪里來的RequestMappingHandlerMapping,沒錯就是文章一開始我們去看初始化DispatcherServlet時在靜態塊里面完成的加載已經后續的初始化,所以按理說,下面的數組中就存在兩個handlerMapping分別是BeanNameUrlHandlerMappingRequestMappingHandlerMapping

第二個問題: 什么是HandlerMapping? 直接看它的中文翻譯就是處理器映射器是不好理解的,其實也沒有特別難懂, 就是一個映射器嘛,映射什么呢? 就是映射用戶的請求與后端程序員編寫的Controller之間的關系, 再直白一點, 就是一個用戶的請求經過了這個HandlerMapping就可以百分百確定出哪一個控制器是用來處理它的

第三個問題: 下面的HandlerMapping是個數組,意味着這個映射的規則是多種多樣的,所以來個循環,如果沒有任何一個映射器滿足條件怎么辦呢? 404唄

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping hm : this.handlerMappings) {
				if (logger.isTraceEnabled()) {
					logger.trace(
							"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
				}

				HandlerExecutionChain handler = hm.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

於是經過了上面HandlerMapping的處理我們獲取出來了一個 HandlerExecutionChain ,並且我們百分百確定這個 HandlerExecutionChain 就是用來處理當前的請求的,但是!!! 我們卻不能直接使用,因為我們是不清楚前面的獲取到的這個執行器鏈是個方法,還是個類,於是適配器就用上了

源碼如下:

這個適配器就是HandlerAdapter,使用設配器設計模式,不管得到的handler到底是什么類型的,都可以找到正確的方法區執行它

HandlerAdapter同樣是DisapcherServlet的一大組件,它和上面的處理器映射器是一樣的,同樣是從配置文件中被讀取出來


	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter ha : this.handlerAdapters) {
				if (logger.isTraceEnabled()) {
					logger.trace("Testing handler adapter [" + ha + "]");
				}
				if (ha.supports(handler)) {
					return ha;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

咋樣? 其實本文倒是也沒什么難度,就是覺得確實挺好玩的...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM