zoukankan      html  css  js  c++  java
  • 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]

    目录

    前言

    SpringMVC是目前主流的Web MVC框架之一。 

    如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

    我们使用浏览器通过地址 http://ip:port/contextPath/path进行访问,SpringMVC是如何得知用户到底是访问哪个Controller中的方法,这期间到底发生了什么。

    本文将分析SpringMVC是如何处理请求与Controller之间的映射关系的,让读者知道这个过程中到底发生了什么事情。

    源码分析

    在分析源码之前,我们先了解一下几个东西。

    1.这个过程中重要的接口和类。

    HandlerMethod类:

      Spring3.1版本之后引入的。 是一个封装了方法参数、方法注解,方法返回值等众多元素的类。

      

      它的子类InvocableHandlerMethod有两个重要的属性WebDataBinderFactory和HandlerMethodArgumentResolverComposite, 很明显是对请求进行处理的。

      InvocableHandlerMethod的子类ServletInvocableHandlerMethod有个重要的属性HandlerMethodReturnValueHandlerComposite,很明显是对响应进行处理的。

      ServletInvocableHandlerMethod这个类在HandlerAdapter对每个请求处理过程中,都会实例化一个出来(上面提到的属性由HandlerAdapter进行设置),分别对请求和返回进行处理。  (RequestMappingHandlerAdapter源码,实例化ServletInvocableHandlerMethod的时候分别set了上面提到的重要属性)

      

    MethodParameter类:

      HandlerMethod类中的parameters属性类型,是一个MethodParameter数组。MethodParameter是一个封装了方法参数具体信息的工具类,包括参数的的索引位置,类型,注解,参数名等信息。

      HandlerMethod在实例化的时候,构造函数中会初始化这个数组,这时只初始化了部分数据,在HandlerAdapter对请求处理过程中会完善其他属性,之后交予合适的HandlerMethodArgumentResolver接口处理。

      以类DeptController为例:

    @Controller
    @RequestMapping(value = "/dept")
    public class DeptController {
    
      @Autowired
      private IDeptService deptService;
      
      @RequestMapping("/update")
      @ResponseBody
      public String update(Dept dept) {
        deptService.saveOrUpdate(dept);
        return "success";
      }
      
    }
    

      (刚初始化时的数据)  

      (HandlerAdapter处理后的数据)

    RequestCondition接口:

      Spring3.1版本之后引入的。 是SpringMVC的映射基础中的请求条件,可以进行combine, compareTo,getMatchingCondition操作。这个接口是映射匹配的关键接口,其中getMatchingCondition方法关乎是否能找到合适的映射。

      

    RequestMappingInfo类:

      Spring3.1版本之后引入的。 是一个封装了各种请求映射条件并实现了RequestCondition接口的类。

      有各种RequestCondition实现类属性,patternsCondition,methodsCondition,paramsCondition,headersCondition,consumesCondition以及producesCondition,这个请求条件看属性名也了解,分别代表http请求的路径模式、方法、参数、头部等信息。

      

    RequestMappingHandlerMapping类:

       处理请求与HandlerMethod映射关系的一个类。

    2.Web服务器启动的时候,SpringMVC到底做了什么。

    先看AbstractHandlerMethodMapping的initHandlerMethods方法中。

    我们进入createRequestMappingInfo方法看下是如何构造RequestMappingInfo对象的。

    PatternsRequestCondition构造函数:

    类对应的RequestMappingInfo存在的话,跟方法对应的RequestMappingInfo进行combine操作。

    然后使用符合条件的method来注册各种HandlerMethod。

    下面我们来看下各种RequestCondition接口的实现类的combine操作。

    PatternsRequestCondition:

    RequestMethodsRequestCondition:

    方法的请求条件,用个set直接add即可。

    其他相关的RequestConditon实现类读者可自行查看源码。

    最终,RequestMappingHandlerMapping中两个比较重要的属性

    private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();

    private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();

    T为RequestMappingInfo。

    构造完成。

    我们知道,SpringMVC的分发器DispatcherServlet会根据浏览器的请求地址获得HandlerExecutionChain。

    这个过程我们看是如何实现的。

    首先看HandlerMethod的获得(直接看关键代码了):

    这里的比较器是使用RequestMappingInfo的compareTo方法(RequestCondition接口定义的)。

    然后构造HandlerExecutionChain加上拦截器

    实例

    写了这么多,来点例子让我们验证一下吧。

    @Controller
    @RequestMapping(value = "/wildcard")
    public class TestWildcardController {
      
      @RequestMapping("/test/**")
      @ResponseBody
      public String test1(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "TestWildcardController -> /test/**");
        return view;
      }
      
      @RequestMapping("/test/*")
      @ResponseBody
      public String test2(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "TestWildcardController -> /test*");
        return view;
      }
      
      @RequestMapping("test?")
      @ResponseBody
      public String test3(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "TestWildcardController -> test?");
        return view;
      }
      
      @RequestMapping("test/*")
      @ResponseBody
      public String test4(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "TestWildcardController -> test/*");
        return view;
      }
      
    }
    

    由于这里的每个pattern都带了*因此,都不会加入到urlMap中,但是handlerMethods还是有的。

    当我们访问:http://localhost:8888/SpringMVCDemo/wildcard/test1的时候。

    会先根据 "/wildcard/test1" 找urlMap对应的RequestMappingInfo集合,找不到的话取handlerMethods集合中所有的key集合(也就是RequestMappingInfo集合)。

    然后进行匹配,匹配根据RequestCondition的getMatchingCondition方法。

    最终匹配到2个RequestMappingInfo:

    然后会使用比较器进行排序。

    之前也分析过,比较器是有优先级的。

    我们看到,RequestMappingInfo除了pattern,其他属性都是一样的。

    我们看下PatternsRequestCondition比较的逻辑:

    因此,/test*的通配符比/test?的多,因此,最终选择了/test?

    直接比较优先于通配符。

    @Controller
    @RequestMapping(value = "/priority")
    public class TestPriorityController {
      
      @RequestMapping(method = RequestMethod.GET)
      @ResponseBody
      public String test1(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "其他condition相同,带有method属性的优先级高");
        return view;
      }
      
      @RequestMapping()
      @ResponseBody
      public String test2(ModelAndView view) {
        view.setViewName("/test/test");
        view.addObject("attr", "其他condition相同,不带method属性的优先级高");
        return view;
      }
      
    }
    

     这里例子,其他requestCondition都一样,只有RequestMethodCondition不一样。

    看出,方法多的优先级越多。

    至于其他的RequestCondition,大家自行查看源码吧。

    资源文件映射

    以上分析均是基于Controller方法的映射(RequestMappingHandlerMapping)。

    SpringMVC中还有静态文件的映射,SimpleUrlHandlerMapping。

    DispatcherServlet找对应的HandlerExecutionChain的时候会遍历属性handlerMappings,这个一个实现了HandlerMapping接口的集合。

    由于我们在*-dispatcher.xml中加入了以下配置:

    <mvc:resources location="/static/" mapping="/static/**"/>

     Spring解析配置文件会使用ResourcesBeanDefinitionParser进行解析的时候,会实例化出SimpleUrlHandlerMapping。

    其中注册的HandlerMethod为ResourceHttpRequestHandler。

    访问地址:http://localhost:8888/SpringMVCDemo/static/js/jquery-1.11.0.js

    地址匹配到/static/**。

    最终SimpleUrlHandlerMapping找到对应的Handler -> ResourceHttpRequestHandler。

    ResourceHttpRequestHandler进行handleRequest的时候,直接输出资源文件的文本内容。

    总结

    大致上整理了一下SpringMVC对请求的处理,包括其中比较关键的类和接口,希望对读者有帮助。

    让自己对SpringMVC有了更深入的认识,也为之后分析数据绑定,拦截器、HandlerAdapter等打下基础。

    参考资料

    http://jinnianshilongnian.iteye.com/blog/1684403

    http://my.oschina.net/HeliosFly/blog/212329

  • 相关阅读:
    SoapUI 使用笔记
    git 使用笔记(二)
    git 使用笔记(一)
    jquery 拓展
    hdu 1024 Max Sum Plus Plus (DP)
    hdu 2602 Bone Collector (01背包)
    hdu 1688 Sightseeing (最短路径)
    hdu 3191 How Many Paths Are There (次短路径数)
    hdu 2722 Here We Go(relians) Again (最短路径)
    hdu 1596 find the safest road (最短路径)
  • 原文地址:https://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html
Copyright © 2011-2022 走看看