SpringMVC運行原理(附源碼追蹤)

(直接看答案可點擊上面 “一句話總結:SpringMVC運行原理”

什麼是SpringMVC?

SpringMVC是對Servlet的封裝
SpringMVC是對Servlet的封裝
SpringMVC是對Servlet的封裝
在沒有使用SpringMVC框架的時候,如果想寫一個Servlet請求分發器,我們會怎麼寫呢?

//接收除jsp外的所有請求,再進行分發
@WebServlet("/")
public class DispatcherServlet extends HttpServlet {

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String result = req.getParameter("control");
		if (result.equals("m1")) {
			method1();
		} else if (result.equals("m2")) {
			method2(req, resp);
		} else if (result.equals("m3")) {
			method3();
		}
	}

	private void method1() {
	}

	private void method2(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.getRequestDispatcher("demo.jsp").forward(req, resp);
	}

	private void method3() {
	}

}

以上是我們自定義的DispatcherServlet,但是SpringMVC有它自己的想法,SpringMVC對Servlet的封裝思想,跟上述代碼是類似的。

SpringMVC的重要組件

爲了完成請求分發和處理,SpringMVC設計了以下組件

//前端控制器(分發器);相當於@WebServlet("/")
DispatcherServlet ds

//映射器,解析請求格式,判斷要執行哪個方法
//相當於 if(req.getParameter("control").equals("m1"))
HandlerMapping hm

//適配器,負責調用具體的方法;相當於method1();
HandlerAdapter ha

//視圖解析器,解析結果,準備跳轉到具體的物理視圖
//相當於req.getRequestDispatcher("demo.jsp").forward(req, resp);
ViewResolver vr 

SpringMVC 的 DispatcherServlet

當SpringMVC接收到請求時,首先會創建前端控制器DispatcherServlet
在這裏插入圖片描述
這裏可以看到,DispatcherServlet 繼承自 HttpServlet。而我們知道,Servlet首次加載時,調用順序是 init() -> service() -> destroy(),我們一個方法一個方法地看。


  • init()

HttpServletBean.class中(留意中文註釋即可)

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

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Let subclasses do whatever initialization they like.
		//進入
		initServletBean();

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

FrameworkServlet.class中

	protected final void initServletBean() throws ServletException, BeansException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			//進入
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (BeansException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}
	protected WebApplicationContext initWebApplicationContext() throws BeansException {
		//SpringMVC容器(ConfigurableWebApplicationContext)
		WebApplicationContext wac = findWebApplicationContext();
		if (wac == null) {
			// No fixed context defined for this servlet - create a local one.
			//Spring容器
			WebApplicationContext parent =
					WebApplicationContextUtils.getWebApplicationContext(getServletContext());
			//spring容器是springmvc容器的父容器
			wac = createWebApplicationContext(parent);
		}

		if (!this.refreshEventReceived) {
			// Apparently not a ConfigurableApplicationContext with refresh support:
			// triggering initial onRefresh manually here.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

在這裏插入圖片描述

至此,init方法結束,可以看到init()主要是創建了springmvc容器。


  • 下面我們來看service()

DispatcherServlet.class中

	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String requestUri = new UrlPathHelper().getRequestUri(request);
			logger.debug(
					"DispatcherServlet with name '" + getServletName() + "' received 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.
		//把請求參數做成一個HashMap保存
		Map attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			logger.debug("Taking snapshot of request attributes before include");
			attributesSnapshot = new HashMap();
			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());

		try {
			//進入
			doDispatch(request, response);
		}
		finally {
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		int interceptorIndex = -1;

		// 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 = new ServletRequestAttributes(request);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

		if (logger.isDebugEnabled()) {
			logger.debug("Bound request context to thread: " + request);
		}
		
		try {
			ModelAndView mv = null;
			try {
				processedRequest = checkMultipart(request);

				// Determine handler for the current request.
				//mappedHandler相當於是HandlerMapping映射器;解析並匹配 handler
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Apply preHandle methods of registered interceptors.
				//調用攔截器方法(與本主題無關)
				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;
					}
				}

				// Actually invoke the handler.
				// 適配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				//HandlerAdapter調用方法;生成ModelAndView
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// Do we need view name translation?
				if (mv != null && !mv.hasView()) {
					mv.setViewName(getDefaultViewName(request));
				}

				// Apply postHandle methods of registered interceptors.
				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);
			}

			// Did the handler return a view to render?
			if (mv != null && !mv.wasCleared()) {
				render(mv, processedRequest, response);
			}
			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);
			}

			// Reset thread-bound context.
			RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
			LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);

			// Clear request attributes.
			requestAttributes.requestCompleted();
			if (logger.isDebugEnabled()) {
				logger.debug("Cleared thread-bound request context: " + request);
			}
		}
	}

值得注意的是,HandlerMapping和HandlerAdapter都是在DispatcherServlet類的方法中被使用的。我們再來看看HandlerMapping和HandlerAdapter內部都做了什麼?
選取HandlerMapping的實現類SimpleUrlHandlerMapping和HandlerAdapter的實現類SimpleControllerHandlerAdapter進行考察。
首先在springmvc.xml 配置文件中

<!-- 控制器 -->
<bean id="demo123" class="com.bjsxt.controller.DemoController"></bean>

<bean
	class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	<property name="urlMap">
		<map>
			<!-- 解析出來的控制器邏輯名 -->
			<entry key="demo" value-ref="demo123"></entry>
		</map>
	</property>
</bean>

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>

當程序加載springmvc.xml時,SimpleUrlHandlerMapping.class中

	protected void registerHandlers(Map urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
		}
		else {
			Iterator it = urlMap.keySet().iterator();
			while (it.hasNext()) {
				String url = (String) it.next();
				Object handler = urlMap.get(url);
				// Prepend with slash if not already present.
				//這裏代表上述key="demo"中無論是否加 /,最終都有/
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String) {
					handler = ((String) handler).trim();
				}
				//url對應“/demo”;handler對應“demo123”
				//進入
				registerHandler(url, handler);
			}
		}
	}
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			if (getApplicationContext().isSingleton(handlerName)) {
				//這裏拿到了id="demo123"的bean,即對應的控制器
				resolvedHandler = getApplicationContext().getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map handler [" + handler + "] to URL path [" + urlPath +
						"]: There is already handler [" + resolvedHandler + "] mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isDebugEnabled()) {
					logger.debug("Root mapping to handler [" + resolvedHandler + "]");
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isDebugEnabled()) {
					logger.debug("Default mapping to handler [" + resolvedHandler + "]");
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				// 做一個(解析後的)url路徑和對應控制器方法的映射表
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isDebugEnabled()) {
					logger.debug("Mapped URL path [" + urlPath + "] onto handler [" + resolvedHandler + "]");
				}
			}
		}
	}

再來看SimpleControllerHandlerAdapter.class

	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 調用控制器方法
		// 本例中是調用(id="demo123"的)com.bjsxt.controller.DemoController中的方法
		return ((Controller) handler).handleRequest(request, response);
	}

一句話總結:SpringMVC運行原理

①.當在web.xml中設置DispatcherServlet的爲 / ,用戶發起請求,DispatcherServlet接收。
②.DispatcherServlet調用HandlerMapping解析URL,並找到對應的控制器方法(Handler對象)
③. DispatcherServlet調用HandlerAdapter,HandlerAdapter調用Controller中的方法
④.方法執行完成後會返回一個ModelAndView對象
⑤. ViewResovler對ModelAndView進行視圖解析,返回View
⑥. DispatcherServlet對View(如jsp)進行渲染(即將模型數據填充至視圖中)
⑦. DispatcherServlet響應用戶
(DispatcherServlet - 前端控制器;Controller - 後端控制器)

ps:前端設計模式

圍繞 (web.xml中配置的) DispatcherServlet進行初始化,即DispatcherServlet做中央處理器。

  • DispatcherServlet中initStrategies方法
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章