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方法的映射就已经创建好了)