springboot HandlerMapping映射器处理原理(一)

主要内容:

        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,再封装拦截器,如何封装拦截器可以看另一篇文章:

springboot拦截器自动配置原理

如果一个请求没有找到具体的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对象后,下一步就是确定当前请求的处理程序适配器,然后执行了


版权声明:本文为Eaeyson原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。