「这是我参与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方法并传入ServletRequest、ServletResponse和FilterChain作为参数。
请求到Servlet的映射
浏览器发出的请求中包含了许多的信息,其中就包括完整的URI路径。服务器处理请求包括两部分:
- 根据URI路径,找到匹配的
ServletContext。(如何选择匹配的ServletContext不在本文的讨论范围) - 在
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
AbstractHandlerMapping:HandlerMapping 接口实现的抽象类,支持排序、默认handler、handler拦截器。它有两个子类:
- AbstractHandlerMethodMapping: 定义了request与handlerMethod的映射关系。T是映射关系,包含了匹配所需的条件。
-
例如,RequestMappingInfoHandlerMapping使用RequestMappingInfo作为映射关系
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> -
RequestMappingInfo,封装了以下类型的映射匹配条件:
- PatternsRequestCondition
- RequestMethodsRequestCondition
- ParamsRequestCondition
- HeadersRequestCondition
- ConsumesRequestCondition
- ProducesRequestCondition
- RequestCondition (optional, custom request condition)
-
实现类RequestMappingHandlerMapping,根据类或方法上的@RequestMapping注解创建RequestMappingInfo对象
-
- AbstractUrlHandlerMapping: URL与handler的映射关系。它又包含了两个具体的实现类:
SimpleUrlHandlerMappingBeanNameUrlHandlerMapping
03.1-RequestMappingHandlerMapping
查找请求中路径对应的handler方法的调用过程如下:
RequestMappingHandlerMapping#getHandler → RequestMappingHandlerMapping#getHanderInternal → RequestMappingHandlerMapping#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#getHandler → BeanNameUrlHandlerMapping#getHanderInternal → BeanNameUrlHandlerMapping#lookupHandler
类中维护了一个Map:
private final Map<String, Object> handlerMap = new LinkedHashMap();
我看到这里是曾有个疑问,怎么才能将容器中的Bean与URI中的路径关联起来呢?后来经过查资料发现如果Bean在容器中注册的名称(或别名)以“/"开头,Spring MVC就会在handerMap中存在维护一条映射关系。
例如:
@Controller(value = "/beanNameController")
public class BeanNameController {
}
在handlerMap中就会存在如下的映射关系:
还有一点需要注意的是,虽然当你请求"localhost:8080/webmvc/beanNameController"时,DispatchServlet能在Bean容器中找到BeanNameController@5720这个Bean实例,但是在后续获得适配器时会出错,抛出如下的异常:
解决办法也很简单。
- 让BeanNameController实现org.springframework.web.HttpRequestHandler接口,获取适配器时使用org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter。
- 让BeanNameController实现org.springframework.web.servlet.mvc.Controller接口,获取适配器时,使用org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter。
- 自定义适配器,使其支持对BeanNameController适配。
03.3-SimpleUrlHandlerMapping
与BeanNameUrlHandlerMapping类似相同,其基本功能都是现在AbstractUrlHandlerMapping类中。
在三个HandlerMapping中,优先级最低。它内部也是维护了一个哈希表,并且有一条映射规则:
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
它们的类关系结构图如下所示:
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方法。
---- 往期文章推荐 ----
Spring MVC 「3」从DispatchServlet开始,一个请求的处理流程