SpringMVC:(一)Request与Controller方法映射的创建

前言:

  在使用SpringMVC的时候有没有想过,为什么在controller类的方法上,添加一个@RequestMapping("/toIndex")注解的时候,从浏览器的输入localhost:8080/toIndex请求就可以到达指定的controller方法呢?

  接下来我们就开始讲Request与Controller方法映射的创建

 

DispatcherServlet:

SpringMVC和在Tomcat整合的时候,会在Tomcat启动的时候加载DispatcherServlet这个类。由于这个类本身就是Servlet容器,所以启动的时候会去执行Servlet的init()方法进行Springmvc/Web环境的初始化,

init()方法由它的父类HttpBeanServlet进行实现。大概流程图如下:

 

通过以上init()方法的初始化流程,最终会到initStrategies(context)这个方法,这个方法中有调用initHandlerMappings(context)方法,完成HandlerMapping的初始化:

 

 注:

  在加载DispatcherServlet这个类的时候,Spring的环境已经初始化完成了,通过DispatherServlet的构造方法传递一个WebApplicationContext的实现类,然后DispatcherServlet就持有了Spring上下文环境(包括工厂,以及注册的bean)。

 

 

下面是初始化HandlerMapping的主要流程详解:

也是Request与Controller方法映射创建的关键

 


 

HandlerMapping

  该类会根据请求来返回对应的Controller方法实例给DispatcherServlet,因此相关的映射便是在HandlerMapping中实现的。

  由于DispatcherServlet会使用HandlerMapping的能力,所以在DispatcherServlet中会去对HandlerMapping进行初始化。

 

HandlerMapping的初始化:

  DispatcherServlet中的initHandlerMappings()方法会去对HandlerMapping进行初始化。该方法调用在该类的initStrategies方法中(initStrategies()方法是在onRefresh方法中调用的

进入到DispatcherServlet中的initHandlerMappings()方法。

  (1)将类中存放HandlerMapping的handlerMappings成员变量置为空值

  (2)判断是否需要从容器里扫描已注册的HandlerMapping实现类并排序好存入handlerMappings成员变量中。(默认是true)

      True:

        (1)寻找IOC容器中HandlerMapping类型的Bean

            这里获取到的Bean有:RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping

        (2)获取到的Bean集合不是空的话对其进行排序后存入handlerMappings成员变量中

 

 

 


RequestMappingHandlerMapping:

  负责处理RequestMapping标签的

  该类会先在容器中给创建出来,后续才能在DispatcherServlet的initHandlerMappings()方法中注入实例进去。

    在RequestMappingHandlerMapping创建的时候会去调用到父类(AbstractHandlerMethodMapping)的afterPropertiesSet()方法去调用initHandlerMethods() :去初始化映射关系

  

 

RequestMappingHandlerMapping的关系图:

 

 

 

 

该类实现了ApplicationContextAware和ServletContextAware,使之能获取到容器的能力,可以操作容器内部的bean。

该类实现类InitializingBean接口,该接口的afterPropertiesSet()方法是在bean初始化的时候执行的。所以在该类中可以对标记有RequestMapping的bean进行处理。

  进入到AbstractHandlerMethodMapping类中,该类实现InitializingBean接口的afterPropertiesSet()方法。方法中调用了initHandlerMethods()方法。该方法主要就是用来初始化处理方法实例

protected void initHandlerMethods() {
        //遍历容器里所有的BeanName
        for (String beanName : getCandidateBeanNames()) {//***
            //忽略掉scopedTarget.打头的bean(session application request之类的作用域内的代理类)
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {//对bean进行筛选,
                processCandidateBean(beanName);//如果类上有Controller注解或RequestMapping注解:提取url与Controller映射关系
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
}

首先我们进入到getCandidateBeanNames()方法,该方法会从容器中去获取所有的bean

protected String[] getCandidateBeanNames() {
        //从root容器以及子容器里,或者仅从子容器里获取所有的Bean
        //在前期如果root容器里有bean被标记RequestMapping,下面的变量就会为true。
        return (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));//只从子容器(servletContext)中查找所有Bean实例的名字
}

然后我们会对获取到的所有BeanName进行筛选(忽略过标了scopeTarget属性作用域的代理类),然后对筛选过后的BeanName用processCandidateBean()方法进行提取url与Controller映射关系。

processCandidateBean():该方法会去获取到bean的Class然后提取其url与Controller映射关系

  (1)根据bean名字获取Bean的Class对象

  (2)beanType !=null &&isHandler(beanType)  =  (Bean的Class不是空的 && Class上有@Controller或@RequestMapping注解)

    条件成立的话会调用detectHandlerMethods(beanName)方法提取其(目标Bean的)url与Controller映射关系,为后续提取并建立Controller方法和请求的映射做准备。

      (A)获取到对应的Bean的Class对象 (如果没有获取到对应的Class对象则不执行后面的逻辑)

      (B)获取目标Class对象的真正被代理类(因为Class对象可能是CGLIB创建的代理类,我们要获取到真正的代理对象)

      (C)MethodIntrospector.selectMethods()方法  :会返回一个(Map<Method, T> methods)存储容器以供下面去注册

        寻找方法上有@RequestMapping注解的Method实例。然后建立起(Key:)Controller方法实例与(Value:)RequestMappingInfo的映射关系 

//寻找方法上有@RequestMapping注解的Method实例
//key:方法实例  值:RequestMappingInfo实例
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
  //在执行完selectMethods()方法之后就能建立起Controller方法实例与RequestMappingInfo的映射关系,
  //并将相关的映射保存到methods这个map里,key:方法实例 值:RequestMappingInfo实例
  (MethodIntrospector.MetadataLookup<T>) method -> {
    try {
       //返回的RequestMappingInfo
      return getMappingForMethod(method, userType);//***  //该方法会将目标方法上的RequestMappingInfo和目标类上的RequestMappingInfo合并成一个RequestMappingInfo然后配置上前缀返回(通常不需要配置前缀)
    }
       catch (Throwable ex) {
          throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);
       }
   });

 

    (D)for循环将获取到的Method对象依次注册到HandlerMapping中{

        (1)获取到被AOP代理包装后的真实的方法实例

        (2)将目标类、真实执行的方法、RequestMappingInfo实例传递 到registerHandlerMethod()方法中去注册(注册进HandlerMethod中

      }

//将获取到的Method对象依次注册到HandlerMapping中去
methods.forEach((method, mapping) -> {
      //由于可能获取到的是没包装过的被代理类的方法,所以重新获取到被AOP代理包装后的方法实例
       Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);//获取被AOP代理包装后的方法实例
      //handler:目标类    invocableMethod:真正执行的方法  mapping:RequestMappingInfo实例 
      registerHandlerMethod(handler, invocableMethod, mapping);//注册进HandlerMethod中  //***
});
registerHandlerMethod()方法:会去调用成员变量(mappingRegistry).register()去进行注册
我们跟进到this.mappingRegistry.register(mapping, handler, method)方法中:
  (1)上锁
  (2)校验要注册的方法的唯一性,即先前是否已经注册过同样的映射(有则抛异常)
  (3)注册RequestMappingInfo 和 HandlerMethod(往mappingLookup(成员变量)中存放进去)
  (4)注册请求路径与对应RequestMappingInfo(获取到mapping中所有的请求路径,然后for循环将url与mapping一 一对应的存入到urlLookup(成员变量)中)
  (5)获取到执行方法真正的命名 和 执行方法(handlerMethod)存入nameLookup中
  (6)注册HandlerMethod与跨域信息
  (7)创建及注册MappingRegistation信息(执行到这就真正的将方法注册进lHandlerMethod,此时请求(Request)与Controller方法的映射就已经创建好了

 

posted @ 2021-03-07 22:22  _kerry  阅读(617)  评论(0编辑  收藏  举报