3

Spring MVC源码学习笔记

 1 year ago
source link: https://www.r9it.com/20221118/spring-mvc.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

万维钢-《才华与野心》

学问能让你 [配得上] 财富,但是并不一定能带来财富。

但如果一个人搞不清自己到底想要什么,一边说着要求知一边又羡慕人家的成就,自己又没有太大野心去狠心做事,那就是人生观不够自洽。

# 01 - Spring MVC 主要流程

先看一张 Spring MVC 请求响应的具体流程图:

image-469290b53ca5f50a12bd24925a00f022.jpg
  1. 客户端发送请求到前端控制器 DispatcherServlet
  2. DispatcherServlet 找到合适的处理器(HandleMapping) ,并生成处理器执行链(HandlerExecutionChain)返回给 DispatcherServlet。
  3. 调用拦截器的 preHandle() 方法,如果返回 false, 则直接返回,不在进行后面的处理。
  4. DispatcherServlet 根据 HandlerMapping 找到处理器适配器(HandlerAdapter)。
  5. 执行 HandlerAdapter.handle()方法,返回一个 ModelAndView 对象。
  6. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器, 返回一个视图对象(View)。
  7. 调用拦截器的 postHandle() 方法。
  8. DispatcherServlet 调用模板引擎工具(JSP,Freemarker,Thymeleaf 等)对 View 进行渲染视图(即将模型数据 model 填充至视图中)。
  9. 调用拦截器的 afterCompletion() 方法。
  10. DispatcherServlet 将渲染结果返回给客户端。

SpringMVC 的请求入口在 FrameworkServlet::processRequest(request, response) 方法, 调用 DispathcherServlet::doService(request, response) 方法来初始化 DispatcherServlet

主要执行过程实现在 DispatcherServlet::doDispatch() 方法中:

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);

				// 进行映射
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 适配 HandlerMapper, 找到最合适的 HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.  HTTP缓存相关
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// 触发前置拦截器:preHandle()
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					// 返回false就不进行后续处理了
					return;
				}

				// Actually invoke the handler.
				// 调用对应的控制器方法(handleRequest(request, response)),返回一个 ModelAndView 对象
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// 如果mv有  视图没有,给你设置默认视图
				applyDefaultViewName(processedRequest, mv);
				//触发后置拦截器:postHandle()
				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);
			}
			// 渲染视图,并触发拦截器:afterCompletion()
			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);
				}
			}
		}
	}
Copied!

逻辑非常清晰,看完这个方法基本上就对 SpringMVC 实现流程有了大体的了解。然后再进入对应的核心方法内部去看具体细节流程。

# 02 - 初始化 DispatcherServlet

DispatcherServlet 的初始化工作是在 DispatcherServlet.initStrategies() 方法中进行的:

protected void initStrategies(ApplicationContext context) {
	// 初始化文件上传解析器
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	// 初始化 HandlerMapping
	initHandlerMappings(context);
	// 初始化 HandlerAdapter
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	// 初始化视图解析器
	initViewResolvers(context);
	initFlashMapManager(context);
}
Copied!

各种资源的初始化的一般逻辑都是先到 Spring Bean 容器中去找,找不到的话就使用 DispatcherServlet.properties 文件中配置的默认资源,以 HandlerMapping 为例,它具体的初始化逻辑如下:

  1. 去当前 Bean 工厂以及父 Bean 工厂中去查找 HandlerMapping 类别的 Bean,找到了就直接排序后赋值给 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);
    }
    
    Copied!

    这里传入的 includeNonSingletons=true, allowEagerInit=false,表明结果中包含非单例 Bean,但是不包含半成品 Bean

  2. 如果根据类型没有找到,则通过 context.getBean() 方法获取 beanName 为 handlerMapping 类型 为 HandlerMapping 的 Bean:

    HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
    this.handlerMappings = Collections.singletonList(hm);
    
    Copied!
  3. 如果还是没有找到 , 就去拿默认的, 默认有三个:BeanNameUrlHandlerMapping, RequestMappingHandlerMapping, RouterFunctionMapping

    if (this.handlerMappings == null) {
    	this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
    
    Copied!

DispatcherServlet.properties 文件内容:

# 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.

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.function.support.RouterFunctionMapping

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.function.support.HandlerFunctionAdapter


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
Copied!

# 03 - 查找 HandlerMapping 流程

DispatcherServlet 查找 HandlerMapping 的流程很简单,就是遍历所有的 HandlerMapping,然后一个一个匹配 Request,匹配上了就立刻返回,不在继续匹配,意味着谁解析到就用谁。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			// 匹配到一个立即返回,不再继续匹配
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}
Copied!

进一步追踪 HandlerMapping.getHandler() 方法,这个方法具体实现是在抽象类 AbstractHandlerMapping 中,主要逻辑代码如下(去掉了一些不重要的代码),注意看注释:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // K2: 核心方法,通过请求 URI 来解析到请求对应的 Handler Method
	Object handler = getHandlerInternal(request);
	// 如果没匹配到,则获取默认的 Handler,也就是那个匹配 "/*" 的 Handler
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
    ...
    // 生成一个控制器执行链
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    ...
	return executionChain;
}
Copied!

在生成控制器执行链的时候,会把当前请求匹配到的所有拦截器加入到执行链。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
	// 筛选出所有跟当前请求匹配的拦截器
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			// 如果匹配上了,就把拦截器加入执行链
			if (mappedInterceptor.matches(request)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}
Copied!

# 04 - 控制器 RequestMap 匹配流程

这是一个非常核心的流程,即如何通过请求 Path 和请求 METHOD 找到对应的控制器和方法。

  1. 通过 path 作为 Key 直接去 pathLookup Map 中查找,找到了就直接使用。

  2. 如果无 path 匹配,用所有的 RequestMappingInfo,通过 AntPathMatcher 匹配,也就是解析表达式。

  3. 如果匹配到多个,则对所有匹配结果进行优先级排序,排序后选择第一个作为最终匹配结果。

    示例假如请求 path 是 `/test`,Matcher 匹配到 4 个结果:

    • @RequestMapping(value="/test?")
    • @RequestMapping(value="/test*")
    • @RequestMapping(value="/{xxxx}")
    • @RequestMapping(value="/**")

    则优先级大致就是 ? > * > {} >**

具体代码实现在 AbstractHandlerMethodMapping.lookupHandlerMethod() 方法中,有兴趣去看看详细流程,这里贴出删减版代码:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	if (directPathMatches != null) {
		// 如果根据path能直接匹配的RequestMappingInfo 则用该mapping进行匹配其他条件(method、header等)
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// 如果无path匹配,用所有的RequestMappingInfo  通过AntPathMatcher匹配
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
	}
	if (!matches.isEmpty()) {
		// 选择第一个为最匹配的
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			//创建MatchComparator的匹配器对象
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);

			// 排完序后拿到优先级最高的
			bestMatch = matches.get(0);
		}
		//把最匹配的设置到request中
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		handleMatch(bestMatch.mapping, lookupPath, request);
		//返回最匹配的
		return bestMatch.getHandlerMethod();
	}
	else { // return null
		return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
	}
}
Copied!

# 05 - 匹配 HandlerAdapter 流程完成

匹配 HandlerAdapter 的方法也很简单,就是遍历所有的 HandlerAdapter, 用当前的找到 Handler 去匹配,匹配成功就返回。

HandlerAdapter 主要有三种,分别对应三种控制器:

  1. HttpRequestHandlerAdapter: 对应的 Handler 要实现 HttpRequestHandler 接口。
  2. SimpleControllerHandlerAdapter: 对应的 Handler 要实现 Controller 接口。
  3. RequestMappingHandlerAdapter: 对应通过解析 @RequestMap 注解得到的 Handler。

另外控制器方法的调用流程在 RequestMappingHandlerAdapter.invokeHandlerMethod() 方法中。

# 06 - 渲染视图流程

  1. 解析视图名称,把视图名称拼上在视图解析器中配置好的前缀,后缀,得到模板文件的完整路径。
  2. 通过 ModelAndView 创建视图对象。
  3. 调用 View.render(model, request, response) 方法,填充属性,渲染视图。

详细源码实现在 DispatcherServlet.render() 方法中。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK