Spring MVC : 如何找到请求的目标控制器方法 ?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/andy_zhang2007/article/details/83107637

概述

Spring MVC Web应用开发时,我们会开发出相应的 controller class/method, 关联到相应的url,然后用户在访问系统时,输入正确的url,相应的controller class/method 就会被执行,返回给用户相应的处理结果。那么Spring MVC Web应用内部是如何找到一个请求对应的controller class/method方法呢 ? 本文基于源代码回答此问题。

简单地讲,应用在启动的时候,Spring MVC将配置文件或者通过注解@Controller+@RequestMapping定义的<url,controller class/method>映射关系搜集起来,保存在内存中,然后在请求到达时,根据请求的url(实际上也会使用相应的HTTP method)就能找到相应的 controller class/method 。下面我们分成这两步进行分析。

本文中的源代码基于Spring MVC 4。

初始化 handlerMappings

DispatcherServlet有一个属性handlerMappings,用于在内存中保持配置定义的<url,controller class/method>映射关系。在DispatcherServlet初始化的过程中,该属性会根据配置信息被填充正确的信息。

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

	// detectAllHandlerMappings 表示是否要从上下文中检测所有的 handlerMappings,缺省值为 true
	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		// 从 ApplicationContext 及其祖先级别上下文中找出所有类型为 HandlerMapping 的 bean ,
		// 这些 bean 实在 bean 扫描阶段根据配置或者注解进入到 Spring IoC 容器的。
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			// 对搜集到的 handlerMappings 进行排序
			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.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}

找到当前请求的handler

每个请求到达DispatcherServlet被处理时都会使用其方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)。该方法调用getHandler()来获取当前请求的handler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 遍历所有的 handlerMappings , 找到第一个支持当前请求 request 的那一个并返回一个 HandlerExecutionChain
	// 对象,注意这里是 HandlerExecutionChain 对象,而不是一个什么 handler 对象,
	// 一个 HandlerExecutionChain 的内容是 1 handler + N handler interceptors , 包含了相应的 handler 对象
	for (HandlerMapping hm : this.handlerMappings) {
		if (logger.isTraceEnabled()) {
			logger.trace(
					"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
		}		
		// 如果当前 HandlerMapping hm 支持当前 request, 那就直接使用它;
		// 如果当前 HandlerMapping hm 不支持当前 request, 其 getHandler 方法会返回 null;
		// getHandler 内部会获取 request 的 url 信息 , http method 信息,然后判断自己是否支持该 request,
		// 具体的实现逻辑不同的HandlerMapping实现类会有不同。
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	// 如果没有所有配置的 handlerMappings  都不支持该请求,则返回 null,
	// 此时DispatcherServlet会抛出一个异常NoHandlerFoundException或者向客户端返回一个404,
	// 具体怎么做看开关的设置,缺省是会向客户端返回一个404。
	return null;
}

这里需要注意的是 getHandler方法的返回结果并不是目标handler,而是包含了目标handler的一个HandlerExecutionChain对象。HandlerExecutionChain可以被理解成是Spring对目标handler的封装。
一个HandlerExecutionChain包含一个目标handler,同时也包含了这个handler的前置/后置/完成时拦截器(pre/post/complete HandlerInterceptor)。

相关资料

Spring MVC 的基本概念,组件和核心逻辑

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/83107637