SpringMVC中的handlerMappings对象用法

从SpringMVC源码解析所用的例子,一个http://localhost:9090/web/hi?name=yang请求调用到下面的地方,发现一个特殊的对象handlerMappings,通过请求uri去找handler的时候需要通过这个handlerMapping来找,那么handlerMappings是怎么样的呢?

从断点调试中,我们可以看到这handlerMappings一共有5个,分别是WebMvcConfigurationSupportXXX,RequestMappingHandlerMapping和BeanNameUrlHandlerMapping。

首先,我们先了解一下这个handlerMappings是什么?从官方描述看,handlerMappings是DispatcherServlet内部的一种Bean类型,DispatcherServlet内部有很多Bean类型,包括HandlerMapping,HandlerAdapter,HandlerExceptionResolver,ViewResolver,LocaleResolver ,ThemesResolver,Multipart Resolver和FlashMapManager 。

其中HandlerMapping是一个将请求与用于预处理和后处理的拦截器列表一起映射起来的处理程序,除了WebMvcConfigurationSupportXXX供用户自定义的映射处理器之外,

主要有以下两种内置类型:

(1)RequestMappingHandlerMapping(它支持@RequestMapping修饰的方法)

(2)SimpleUrlHandlerMapping(它为处理程序维护URI路径模式的显式注册)

一.handlerMappings集合内部的元素是什么,有什么作用?

通过官方文档描述,handlerMappings集合内部最主要有两种映射处理器类型RequestMappingHandlerMapping和SimpleUrlHandlerMapping,它们分别用于处理不同类型的Controller。

我们知道可以通过注解@Controller或@RestController注解方式来声明一个Controller,然后使用@RequestMapping来修饰类名或方法名,这种方式其实是由RequestMappingHandlerMapping映射处理器来对请求地址和方法名进行映射的;那么SimpleUrlHandlerMapping又用于处理哪种方式的Controller呢,那就是采用实现Controller接口或实现HttpRequestHandler接口的类,这种声明Controller的方式是早期的做法现在在实际开发中用的比较少了但是也是SpringMVC所支持的。

对于RequestMappingHandlerMapping

这种类型的handlerMapping是我们通过@RequestMapping+请求地址来修饰方法时,会由这么一个映射器来对这种配置方式进行解析和映射处理,也就是说RequestMappingHandlerMapping类型的映射器是为注解声明方式的映射专门设计的,<K,V>映射关系会存储在这个handlerMapping的Map中。

对于SimpleUrlHandlerMapping

这个映射器则是为继承Controller类或实现HttpRequestHandler接口的类进行解析和映射处理的。

用代码举例子来验证,我们分别新建两个类分别实现Controller接口和实现HttpRequestHandler接口,在类中写请求接收方法,那么通过这两种声明的Controller通过请求地址在查询请求方法时就会由SimpleUrlHandlerMapping进行存储和映射,<K,V>映射关系会存储在这个SimpleUrlHandlerMapping的Map中。

(1)实现Controller接口的方式:

(2)实现HttpRequestHandler接口的方式:

然后重新进行debug,使用http://localhost:9090/beanweb或http://localhost:9090/beanweb2请求,就会看到在handlerMapping中查找handler时会跳过第一种注解类型的RequestMappingHandlerMapping,而真正进行解析处理的就是第二种非注解方式SimpleUrlHandlerMapping:

因此我们现在就明白了这个handlerMappings映射处理器集合的数据是怎么样的,集合内存储的映射处理器分别处理怎么样的请求。那么这个集合数据是怎么初始化的呢?

二.handlerMappings集合是怎么初始化的?

通过IDEAJ的Find Usages工具,在本文图一的this.handlerMappings进行调用搜索,可以一层层找到如下调用链:

HttpServletBean.init-->initServletBean()-->FrameworkServlet.initServletBean-->
FrameworkServlet.initWebApplicationContext-->FrameworkServlet.onRefresh
-->DispatcherServlet.onRefresh-->DispatcherServlet.initStrategies-->DispatcherServlet.initHandlerMappings
-->DispatcherServlet.getDefaultStrategies-->strategies.add((T) strategy)

通过前面SpringMVC前两个流程的学习可知,DispatcherServlet继承了FrameworkServlet,同时就集成了HttpServlet,也就是说DispatcherServlet本质上就是一个Servlet,那么在容器加载这个Servlet的时候如果设置了<load-on-startup>加载时马上进行初始化,则在加载时会自动执行其init方法,于是就有了上面调用链的第一个HttpServletBean.init入口。

我们的目的是找到哪里进行了handlerMappings的核心操作,通过调用关系我们找到核心代码在DispatcherServlet.initHandlerMappings:

通过上面代码返回的对象我们可以大胆推测matchingBeans就是我们要了解的handlerMappings集合,这个方法内部完成了对多种类型HandlerMapping的初始化。通过查看我们重点关注的两种映射器对象值,可以分别看到<K,V>关系:

上面就可以看到前面我们提到的urlLookup的Map集合以及handlerMap集合。为了了解这些不同的请求地址是怎么分配给不同类型的映射器来处理的,我们继续点开这个核心方法:

实际由下面这行代码完成了:

lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit)

而真正值得思考的逻辑在于

String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);

上面方法的逻辑就会自动生成一个有5种类型handlerMapping的handlerMappings,那么这5种映射器是怎么产生的呢?

通过核心代码我们可以知道,这里通过传入一个type="HandlerMapping"的类型,在beanDefinitionNames的集合中匹配所有属于HandlerMapping类型的BeanName,然后放入到result数组返回,然后在单例池中根据BeanName获取对应的HandlerMapping类型的Bean。

也就是说这些HandlerMapping映射处理器的生成是通过在BeanDefinition中匹配,如果匹配上了从单例池中获取对应类型的Bean,因此最后的handlerMappings中就有这5种类型了,至于请求路径和方法名怎么作为K,V绑定到对应映射器上的,由于个人阅读源码的能力有限这部分代码小编还尚未理解,作为小编的一个未解之谜吧。

当然,既然5种类型的映射器是通过BD来生成的,那这些BeanDefinition又是什么时候被加到Spring容器中的呢?

这就不得不提一下关于HandlerMapping映射处理器的配置文件:DispatchServlet.properties文件,这个文件就定义了哪些内置使用的组件声明。

实际上,通过上面源码的调试,对于HandlerAdaper的流程和HandlerMapping其实是很类似的。

总结

(1)handlerMappings映射器集合内部一共有5个不同类型的映射器

分别是WebMvcConfigurationSupportXXX,RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,RequestMappingHandlerMapping映射处理器用于使用注解方式请求地址和方法名进行映射的Controller,SimpleUrlHandlerMapping用于实现Controller接口或实现HttpRequestHandler接口的Controller;

(2)handlerMappings映射器集合初始化会经历一个调用链

在DispatchServlet启动时自动开始初始化,

HttpServletBean.init-->initServletBean()-->FrameworkServlet.initServletBean--> FrameworkServlet.initWebApplicationContext-->FrameworkServlet.onRefresh -->DispatcherServlet.onRefresh-->DispatcherServlet.initStrategies-->DispatcherServlet.initHandlerMappings -->DispatcherServlet.getDefaultStrategies-->strategies.add((T) strategy)

初始化的结果是产生一个handlerMappings映射器集合,内部包含5种不同类型的映射器,每种映射器内部由Map<K,V>来维护一个<请求地址,全限定方法名>的映射关系;

(3)handlerMappings映射器集合的每个映射处理器

是在初始化时就生成了BeanDefinition,通过BeanDefinitionName和传入的type=HandlerMapping类型在所有BeanDefinitionNames集合中匹配所有的映射处理器,再从单例池中获取其对应Bean实例放入handlerMappings中;

(4)对于HandlerAdaper的初始化和HandlerMapping流程是类似的

(5)<请求地址全限定方法名>的映射关系

在哪里生成并维护到映射处理器中的,这部分代码小编暂时还没能了解到。

三.handlerMappings有什么扩展?

我们知道,SpringMVC中不能直接通过url来访问WEB-INF下的静态资源,如我们在工程webapp/jsp/新建一个1.jsp文件,通过

localhost:9090/jsp/1.jsp访问时会报如下404错误

但是SpringBoot却可以通过url直接访问webapp,static,resource等包下面的静态资源,为什么会这样呢?原因就在于SpringBoot对SpringMVC的handlerMappings进行了自己的拓展。

之所以SpringMVC不能访问静态资源是因为对于这种访问静态资源的请求,在handlerMappings内没有任何一个默认的映射器可以进行解析处理,因此自然这种请求就会被忽视。

对于SpringBoot来说,它自己实现了一个针对这种访问静态资源的映射处理器并交由handlerMappings来管理,因此就可以做到类似拓展,对于这个知识点在对SpringBoot学习时看能否研究一下这个特殊的映射处理器是怎么样的,这里暂时知道这么一个结论即可。

到此关于handlerMappings对象的研究学习暂时到此,回到SpringMVC的主线流程逻辑中继续学习后续的步骤。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持云海天教程。