主要内容:
springboot如何通过HanlderMapping返回HandlerExecutionChain对象, HandlerExecutionChain对象里面封装了处理当前请求的具体handler(Object类型)和关联的拦截器
未涉及以下内容:
1.如何自定义HandlerMapping以及处理程序适配器
2.执行处理器时,springboot对方法参数的解析和对方法返回值的解析
先创建一个映射请求:
@RequestMapping(value = "/hello")
public String hello(){
return "hello";
}访问请求:
http://localhost:8080/hello
1. 在DispatcherServlet.doDispatch() debug

mappedHandler = getHandler(processedRequest); 用于获取当前请求的处理器handler
2. 进入getHandler(processedRequest)方法, 这个方法就在当前类中

可以看出是通过遍历该类中 this.handlerMappings 集合来寻找合适的 HandlerMapping, 当前集合中的值如下图所示:
这些都是 HandlerMapping接口 的实现类

HandlerMapping接口作用:
用于定义请求和处理程序对象之间映射关系,也就是返回一个 HandlerExecutionChain 对象,如果返回为null,就表示该 HandlerMapping 不能找到请求的映射关系,就会继续遍历循环
3. 进入mapping.getHandler(request)
可以看出第一个 handlerMapping 对象就是 RequestMappingHandlerMapping, handler表示具体的处理器, RequestMappingHandlerMapping返回的handler是HandlerMethod类型,里面封装了方法的相关信息

4. 进入getHandlerInternal(request)
/**
* 查找给定请求的处理程序方法
* 返回HandlerMethod实例
*/
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath =
getUrlPathHelper().getLookupPathForRequest(request); //解析请求路径,lookupPath: "/hello"
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod =
lookupHandlerMethod(lookupPath, request); //实际调用这个方法来查找的
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}5. 进入lookupHandlerMethod(lookupPath,request)
先看一下当前 this.mappingRegistry 中有哪些数据,这个方法会用到:

/**
* 查找当前请求的最佳匹配处理程序方法
* 如果找到多个匹配项,则选择最佳的匹配项
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
/**
*
*/
List<Match> matches = new ArrayList<>();
/**
* (1)this.mappingRegistry表示内部类MappingRegistry实例:
一个注册表,他维护处理程序方法的所有映射,并定义一些方法以执行查找并提供并发访问,自己可以去看一看
* 映射关系使用Map封装,就像下面这样:
* a. private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
* b. private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
* 比如urlLookup的key-value分别是: <请求路径url , LinkedList<RequestMappingInfo>>
*
* (2)getMappingsByUrl(lookupPath):
* 实际调用urlLookup.get(lookupPath);
* 也就是返回给定URL路径的匹配项,不是线程安全的
* 返回的匹配项是 LinkedList<RequestMappingInfo>类型,从(1)中就知道
* RequestMappingInfo类型下面会提到
*/
List<T> directPathMatches =
this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
/**
* 筛选directPathMatches,因为directPathMatches是根据URL匹配到的,我们知道同一个URL可能有多个控制器方法
* 所以再通过请求的方式(如get/post等),对请求头的要求等,找到匹配的
* 具体怎么筛选的可以自己去看一看
*/
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
/**
* 如果matches我空,再遍历this.mappingRegistry中注册的所有映射关系来看有没有匹配的
*/
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
/**
* 获取matches中的Match实例,如果里面有多个,就确定一个最佳的
*/
Match bestMatch = null;
/**
* .......省略如何确定最佳的过程........
*/
/**
* 返回HandlerMathod实例,里面封装了我们控制器的方法,在后续中被执行
*/
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}6. 解决RequestMappingInfo到底是什么? 为什么通过URL获取这个对象的集合
根据类名就大概知道是用来保存请求映射信息的, 也就是一个URL请求映射就被封装成一个RequestMappingInfo实例
@RequestMapping()不就代表一个URL请求映射吗? 即把@RequestMapping注解里面的属性信息封装到RequestMappingInfo中
/**
* springmvc在启动时,会扫描所有的@RequestMapping并封装成对应的RequestMappingInfo
*/
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
/**
*下面这些就用来封装映射条件
*/
private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition();
private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition();
private static final ParamsRequestCondition EMPTY_PARAMS = new ParamsRequestCondition();
private static final HeadersRequestCondition EMPTY_HEADERS = new HeadersRequestCondition();
private static final ConsumesRequestCondition EMPTY_CONSUMES = new ConsumesRequestCondition();
private static final ProducesRequestCondition EMPTY_PRODUCES = new ProducesRequestCondition();
private static final RequestConditionHolder EMPTY_CUSTOM = new RequestConditionHolder(null);
}这些请求条件映射类的继承结构如下:


当前请求中directPathMatches集合中只有有一个RequestMappingInfo实例,这个RequestMappingInfo实例中就记录了这个请求的相关信息,如下:

也就是将@RequestMapping()注解里面的值封装到RequestMappingInfo里面
现在将请求映射更改为: 增加了headers属性
@RequestMapping(value = "/hello",headers = {"Host"})
public String hello(){
return "hello";
}再次访问请求, 此时directPathMatches集合中的RequestMappingInfo信息如下所示:

现在再将请求映射更改为:
@RequestMapping(value = "/hello",headers = {"User-agent"},method = RequestMethod.GET)
public String hello(){
return "hello";
}
@RequestMapping(value = "/hello",headers = {"Host"},method = RequestMethod.POST)
public String hello2(){
return "hello";
}再次访问/hello请求,信息如下图:
此时directPathMatches集合中就有两个RequestMappingInfo实例了,因为directPathMatches得到就是直接根据URL匹配的

展开directPathMatches内容

最终,就找到了具体的handler,然后创建HandlerExecutionChain对象,封装handler,再封装拦截器,如何封装拦截器可以看另一篇文章:
如果一个请求没有找到具体的handler,就继续遍历下一个HandlerMapping了,也就是他:

比如我们此时访问一个不存在的路径:
http://localhost:8080/hello233
经过测试发现处理的HandlerMapping是SimpleUrlHandlerMapping,可以自己尝试, 看SimpleUrlHandlerMapping如何处理的,如何获取具体handler的
下面是SimpleUrlHandlerMapping获取handler的关键代码: 自己也可以debug进入

此时显然handler返回null,this.handlerMap中没有对应的key, 方法继续执行

通过PathMatcher对象来匹配后,就会将 "/**" 放入matchingPatterns,然后再通过 "/**" 从this.handlerMap中获取value,也就是 ResourceHttpRequestHandler对象,至此就结束了
综上,通过HandlerMapping获取HandlerExecutionChain对象后,下一步就是确定当前请求的处理程序适配器,然后执行了