Spring MVC 「4」路径匹配

592 阅读7分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

01-Concepts

在开始今天的主题之前,我们先来回顾一些概念。这能够帮助我们更好地理解今天的内容。

Servlet容器

为Servlet提供运行时环境,常见的Servlet容器实现包括Tomcat、Jetty等。基于Spring MVC构建的WEB应用,其核心DispatcherServlet就是一个Servlet实现。当我们启动容器,部署WEB应用之后,DispatcherServlet就被创建并运行在容器中。

Servlet Context

ServletContext接口个定义了Servlet运行所需要的API,例如记录事件、获取资源的URL引用、与容器中其他Servlet共享信息等。换句话说,ServletContext从Servlet的角度定义Servlet容器应该提供哪些功能。

ServletContext接口的具体实现由Servlet容器供应商提供。

部署在容器中的WEB应用,都有且仅有一个与其相关联的ServletContext实例。

一般来说,ServletContext会挂载到WEB服务器的某个路径上,例如"localhost:8080/catalog",那么所有以 /catalog 开头的请求都将会路由到与该ServletContext相关联的WEB应用中。 /catalog 这个路径一般也被称为 context path

Filter

Filter是一段可复用的代码,用来修改HTTP请求、响应或头信息中的内容。一般来说,它不需要创建响应或像Servlet那样对请求作出回应,而是会修改请求或响应中的内容。

实现自定义Filter,需要实现javax.servlet.Filter接口,并提供无参构造器。

当容器收到一个请求后,会从从Filter列表中取第一个Filter实例,并调用它的doFilter方法并传入ServletRequestServletResponseFilterChain作为参数。

请求到Servlet的映射

浏览器发出的请求中包含了许多的信息,其中就包括完整的URI路径。服务器处理请求包括两部分:

  1. 根据URI路径,找到匹配的ServletContext。(如何选择匹配的ServletContext不在本文的讨论范围)
  2. ServletContext中,尝试查找一个合适的handler来处理请求。(接下来主要讨论的内容) [1]中,进一步把URI路径划分为Context Path / Servlet Path / PathInfo,感兴趣的可以仔细看下。

[1] JSR-000340 JavaTM Servlet 3.1 Final Release for Evaluation

02-DispatcherServlet#doDispatch

Spring MVC 「1」初识Spring MVC 中,我们了解到基于Spring MVC构建的WEB应用是遵循前端控制器模式的,而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 {
		try {
			ModelAndView mv = null;
			Object dispatchException = null;

			try {
				processedRequest = this.checkMultipart(request);
				multipartRequestParsed = processedRequest != request;
				// 本文关注的重点
				// 1. 根据请求,查找到处理该请求的 handler
				// 实际上是 HandlerExecutionChain  对象,包含了一系列要应用的拦截器
				mappedHandler = this.getHandler(processedRequest);
				if (mappedHandler == null) {
					this.noHandlerFound(processedRequest, response);
					return;
				}
				// 2. 找到 handler 对应的适配器,以下三种中选择
				// RequestMappingHandlerAdapter 
				// HttpRequestHandlerAdapter 
				// SimpleControllerHandlerAdapter
				HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
				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;
					}
				}
				// 3. 调用拦截器中的 preHandle 方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				// 4. 通过适配器,调用 handler 方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				this.applyDefaultViewName(processedRequest, mv);
				// 5. 调用拦截器中的 postHandle 方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			} catch (Exception var20) {
				dispatchException = var20;
			} catch (Throwable var21) {
				dispatchException = new NestedServletException("Handler dispatch failed", var21);
			}

			this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
		} catch (Exception var22) {
			this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
		} catch (Throwable var23) {
			this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
		}

	} finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		} else if (multipartRequestParsed) {
			this.cleanupMultipart(processedRequest);
		}

	}
}

根据请求查找对应handler的逻辑在DispatcherServlet#getHandler方法中:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		// handlerMappings 列表会在 DispatcherServlet 关联的容器(及其父容器,默认包括)中
		// 查找所有 HandlerMapping.class 类型的Bean
		// 如果不做特殊配置的话,因该可以找到三个具体类型的Bean:
		// RequestMappingHandlerMapping 
		// BeanNameUrlHandlerMapping 
		// SimpleUrlHandlerMapping
		Iterator var2 = this.handlerMappings.iterator();
		while(var2.hasNext()) {
			HandlerMapping mapping = (HandlerMapping)var2.next();
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}

	return null;
}

03-HandlerMapping

Untitled.png

AbstractHandlerMapping:HandlerMapping 接口实现的抽象类,支持排序、默认handler、handler拦截器。它有两个子类:

  1. AbstractHandlerMethodMapping: 定义了request与handlerMethod的映射关系。T是映射关系,包含了匹配所需的条件。
    1. 例如,RequestMappingInfoHandlerMapping使用RequestMappingInfo作为映射关系

      public abstract class RequestMappingInfoHandlerMapping 
      extends AbstractHandlerMethodMapping<RequestMappingInfo>
      
    2. RequestMappingInfo,封装了以下类型的映射匹配条件:

      1. PatternsRequestCondition
      2. RequestMethodsRequestCondition
      3. ParamsRequestCondition
      4. HeadersRequestCondition
      5. ConsumesRequestCondition
      6. ProducesRequestCondition
      7. RequestCondition (optional, custom request condition)
    3. 实现类RequestMappingHandlerMapping,根据类或方法上的@RequestMapping注解创建RequestMappingInfo对象

  2. AbstractUrlHandlerMapping: URL与handler的映射关系。它又包含了两个具体的实现类:
    1. SimpleUrlHandlerMapping
    2. BeanNameUrlHandlerMapping

03.1-RequestMappingHandlerMapping

查找请求中路径对应的handler方法的调用过程如下:

RequestMappingHandlerMapping#getHandlerRequestMappingHandlerMapping#getHanderInternalRequestMappingHandlerMapping#lookupHandlerMethod

类中维护了一个映射注册表:

private final AbstractHandlerMethodMapping<T>.MappingRegistry mappingRegistry = new AbstractHandlerMethodMapping.MappingRegistry();

查找匹配的过程,即从注册表中查找对应的请求匹配信息RequestMappingInfo:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
	// 1. 从注册表中查询所有与请求中路径匹配的 RequestMappingInfo 对象
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		// 2. 若能找到,则添加到匹配列表中
		this.addMatchingMappings(directPathMatches, matches, request);
	}

	if (matches.isEmpty()) {
		// 若1.未找到匹配的,则将注册表中所有的均添加到匹配列表中
		this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
		// 对匹配列表排序,按优先级降序
		matches.sort(comparator);
		// 取匹配列表第一位,即最匹配的
		AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
		// 若找到多个匹配,且第一、第二匹配的优先级相同(说明匹配有歧义,抛异常)
		if (matches.size() > 1) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace(matches.size() + " matching mappings: " + matches);
			}

			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}

			AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		// 设置属性,值为最匹配的路径对应的方法
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		this.handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	} else {
		return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

03.2-BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping#getHandlerBeanNameUrlHandlerMapping#getHanderInternalBeanNameUrlHandlerMapping#lookupHandler

类中维护了一个Map:

private final Map<String, Object> handlerMap = new LinkedHashMap();

我看到这里是曾有个疑问,怎么才能将容器中的Bean与URI中的路径关联起来呢?后来经过查资料发现如果Bean在容器中注册的名称(或别名)以“/"开头,Spring MVC就会在handerMap中存在维护一条映射关系。

例如:

@Controller(value = "/beanNameController")
public class BeanNameController {
}

在handlerMap中就会存在如下的映射关系:

Untitled.png

还有一点需要注意的是,虽然当你请求"localhost:8080/webmvc/beanNameController"时,DispatchServlet能在Bean容器中找到BeanNameController@5720这个Bean实例,但是在后续获得适配器时会出错,抛出如下的异常:

Untitled.png

解决办法也很简单。

  1. 让BeanNameController实现org.springframework.web.HttpRequestHandler接口,获取适配器时使用org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter。
  2. 让BeanNameController实现org.springframework.web.servlet.mvc.Controller接口,获取适配器时,使用org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter。
  3. 自定义适配器,使其支持对BeanNameController适配。

03.3-SimpleUrlHandlerMapping

BeanNameUrlHandlerMapping类似相同,其基本功能都是现在AbstractUrlHandlerMapping类中。

在三个HandlerMapping中,优先级最低。它内部也是维护了一个哈希表,并且有一条映射规则:

Untitled.png

04-HandlerExecutionChain

在前文三个HandlerMapping具体实现的共同父类AbstractHandlerMapping#getHandler,查找到的handler会被封装为HandlerExecutionChain对象,并且将各种拦截器与其绑定在一起,以便在后面的处理过程中调用拦截器中的pre-post-方法。

05-HandlerAdapter

DispatcherServlet通过具体的HandlerMapping实现查找到请求对应的handler方法后,会再查找该方法对应的适配器。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		// 遍历所有注册的适配器 
		Iterator var2 = this.handlerAdapters.iterator();

		while(var2.hasNext()) {
			HandlerAdapter adapter = (HandlerAdapter)var2.next();
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}

	throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

DispatcherServlet中注册有三个适配器:

RequestMappingHandlerAdapter / HttpRequestHandlerAdapter / SimpleControllerHandlerAdapter

Untitled.png

它们的类关系结构图如下所示:

Untitled.png

HandlerAdapter#supports方法用于判断是否支持作为某个handler的适配器。

  • RequestMappingHandlerAdapter#supports

    return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
    
    // RequestMappingHandlerAdapter#supportsInternal 返回值为true
    
  • HttpRequestHandlerAdapter#supports

    return handler instanceof HttpRequestHandler;
    
  • SimpleControllerHandlerAdapter#supports

    return handler instanceof Controller;
    

06-总结

文章的最后,用一张图来总结一下DispatcherServlet的doDispatch方法。

Untitled.png

---- 往期文章推荐 ----

Spring MVC 「3」从DispatchServlet开始,一个请求的处理流程

Spring MVC 「2」WebApplicationInitializer的工作原理

Spring MVC 「1」初识Spring MVC