深入瞭解SpringMVC原始碼解析

女友在高考 發表於 2021-11-27
Spring

Spring MVC原始碼解析

Spring MVC的使用原理其實是通過配置一個Servlet來接管所有的請求,所有的請求由這個Servlet來進行分發處理。

我們可以從web.xml裡面看出這一點

<servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>

這個Servlet就是Spring MVC提供的DispatcherServlet,它的繼承圖如下:
深入瞭解SpringMVC原始碼解析

DispatcherServlet大致接收請求的時序圖如下:
深入瞭解SpringMVC原始碼解析

下面我們先設定幾個問題然後再去看原始碼。

  1. DispatcherServlet初始化的時機?

我們將斷點打在DispatcherServlet中的onRefresh方法上
,然後啟動專案。

深入瞭解SpringMVC原始碼解析

從這個堆疊的圖可以看出,最開始是HttpServletBean的init方法執行,然後啟動Spring容器,Spring容器初始化的最後一步finishRefresh裡面進行了事件通知。

FrameworkServlet裡有個內部類ContextRefreshListener,實現了ApplicationListener介面,接收到了上面的事件通知,執行onApplicationEvent方法。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

繼續跟蹤onApplicationEvent方法

public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		synchronized (this.onRefreshMonitor) {
			onRefresh(event.getApplicationContext());
		}
	}

發現最終是由onRefresh方法來進行具體處理的,並且這個onRefresh方法在FrameworkServlet裡面是個空方法,是由它的子類DispatcherServlet來實現的。

我們來看DispatcherServlet裡的onRefresh方法

protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

這樣就把SpringMVC的九大元件給初始化了。

  1. 我們的@RequestMapping註解在方法上,SpringMVC是怎麼根據這個註解就能將對應的請求執行到這個方法上的?

從上面可以看到有個initHandlerMappings方法:

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

		if (this.detectAllHandlerMappings) {
			// 找所有實現了HandlerMapping介面的類
			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) {
		//預設情況下,我們是使用的這個方法。
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

繼續跟蹤getDefaultStrategies方法

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			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);
				}
			//省略部分程式碼...
			}
			return strategies;
		}
		else {
			return new LinkedList<>();
		}
	}

可以看出對classNames進行了遍歷,這裡有兩個值,
BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,我們一般用的是RequestMappingHandlerMapping。

RequestMappingHandlerMapping的父類AbstractHandlerMethodMapping實現了InitializingBean介面,我們來看看它實現的afterPropertiesSet方法。

public void afterPropertiesSet() {
		this.config = new RequestMappingInfo.BuilderConfiguration();
		this.config.setUrlPathHelper(getUrlPathHelper());
		this.config.setPathMatcher(getPathMatcher());
		this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
		this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
		this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
		this.config.setContentNegotiationManager(getContentNegotiationManager());

		super.afterPropertiesSet();
	}

前面都是一些配置,後面是呼叫父類的afterPropertiesSet方法,此方法裡只有initHandlerMethods一個方法

protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

主要看processCandidateBean方法

protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
			beanType = obtainApplicationContext().getType(beanName);
		}
		catch (Throwable ex) {
			// An unresolvable bean type, probably from a lazy bean - let's ignore it.
			if (logger.isTraceEnabled()) {
				logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
			}
		}
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}

繼續跟蹤detectHandlerMethods方法

protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			//利用工具,取出類裡面的方法並組裝成map
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			//遍歷map,處理裡面的方法
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

繼續跟蹤裡面的register方法

public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				assertUniqueMethodMapping(handlerMethod, mapping);
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

可以看出mappingLookup、urlLookup、registry都放入了值。這時我也不知道每個的具體作用。我把斷點打到這3個屬性上。然後前端發起一個get請求。

  1. 請求匹配的過程?

前端發起請求,斷點停在了AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl方法上,以前端發起的請求路徑,從urlLookup上獲取對應的值。

深入瞭解SpringMVC原始碼解析

最後看看請求分發的主流程,也是springMVC最核心的程式碼

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 {
				//multipart請求處理
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 獲取合適的HandlerExecutionChain
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				//獲取合適的HandlerAdapter
				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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// handle執行呼叫(核心)
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			//檢視解析並渲染到頁面
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

總結

  1. tomcat的Servlet調起Spring容器啟動,Spring容器啟動完,事件通知到SpringMVC的DispatcherServlet。

  2. 這時會掃描所有的bean,將註解了@Controller和@RequestMapping的解析出來。

  3. 前端請求發過來,DispatcherServlet接收到(因為它是個servlet,配置在web.xml的),根據上一步處理好的對映關係,找到對應的方法來處理。

如通過/test能找到test方法

@RequestMapping("/test")
	public String test(String name, HttpServletRequest request,Model model){
		System.out.println("name");
		model.addAttribute("date",new Date());
		return "success";
	}
  1. 找到對應的方法後,反射呼叫
method.invoke(Object obj,Object... args)
  1. 組裝modelAndView渲染檢視到前端