23 Spring MVC 系列 - 处理器映射器 HandlerMapping
原文连接
到目前为止,已经知道在Spring MVC的架构环境下,用户在web端触发了请求之后,请求会先通过前端控制器 DispatcherServlet,然后DispatcherServlet 会请求处理器映射器 HandlerMapping 寻找处理该请求的 Handler(或带拦截器的Handler链),接着DispatcherServlet 会根据找到的 Handler 与配置的处理器适配器 HandlerAdapter...先到此打住。
也就是说对于用户请求,处理器映射器 HandlerMapping 为前端控制器 DispatcherServlet 和处理器 Handler的交互搭建了桥梁。
HandlerMapping只是一个接口类,不同的实现类有不同的匹对方式,根据功能的不同我们需要在SpringMVC容器中注入不同的映射器处理器HandlerMapping。
简单工作如下:
一、HandlerMapping
接口
1.1 HandlerMapping
注入
在 DispatcherServlet
类中有下面这个方法
public class DispatcherServlet extends FrameworkServlet {
private void initHandlerMappings(ApplicationContext context) {...}
}
容器被初始化的时候该方法会被调用,加载容器中注入的HandlerMapping
。
我们可以在springmvc.xml中设置处理器映射器,现在更多使用Springboot开发时不设置springmvc.xml配置文件时,就会读取spring-webmvcjar包中的默认的配置文件DispatcherServlet.properties
,
可以看到这里配置的HandlerMapping是RequestMappingHandlerMapping。
1.2HandlerExecutionChain
初始化
在DispatcherServlet
类中,doDispatch(..)
方法总通过调用本类的getHandler(..)
方法得到HandlerExecutionChain
对象。
也就是说,这里也就映射了开头所说的前端处理器DispatcherServlet
调用HandlerMapping
获取HandlerExecutionChain
对象,是通过遍历HandlerMapping
的集合对象handlerMappings
来获取的。
1.3 HandlerMapping
接口
在HandlerMapping
接口中只有一个方法。
核心类结构如下图所示:
大致上分为两大类:AbstractHandlerMethodMapping
和AbstractUrlHandlerMapping
,两个都继承AbstractHandlerMapping
实现HandlerMapping
接口。
二、HandlerMapping
接口实现抽象类 AbstractHandlerMapping
在AbstractHandlerMapping类中实现getHandler(...)接口方法得到HandlerExecutionChain对象。AbstractHandlerMapping
实现HandlerMapping
接口,同时也继承WebApplicationObjectSupport
,即服务启动时会自动调用模版方法
2.1 AbstractHandlerMapping
实现类分支之一 AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
:URL 映射的抽象基类,提供将处理程序映射到 Controller
,所以该类最终直接返回的 handler
就是 Controller
对象。
实现父抽象类的抽象方法 getHandlerInternal(..)
匹配并返回对应的 Handler
对象。
接下来咱们看看根据路径匹对 handler
的方法 lookupHandler(..)
从代码中可以看出,这里所谓的查找Handler
就是从this.handlerMap
里根据urlPath
查找,那问题来了,是什么时候怎么样将Handler
放到this.handlerMap
里的呢?
比较开心的是this.handlerMap
这个map对象在当前类中就一个地方进行了put操作。在protected void registerHandler(String urlPath, Object handler) {}
这个方法中进行了put操作。
从源码看是在 AbstractUrlHandlerMapping
子类里面调用该registerHandler
方法。AbstractUrlHandlerMapping
的子类从上面截图的类结构可以看出来,大致分为两类:
- 间接继承
AbstractUrlHandlerMapping
的BeanNameUrlHandlerMapping
- 直接继承
AbstractUrlHandlerMapping
的SimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping
首先来看其父类 AbstractDetectingUrlHandlerMapping
怎么调用 registerHandler(String urlPath, Object handler)
又怎么匹配到配置在容器中的 handler
并将其注入到 AbstractUrlHandlerMapping
的 this.handlerMap
中。
上面的业务实现中,提到判断是否符合当前处理器映射器的判断规则是个抽象方法protected abstract String[] determineUrlsForHandler(String beanName);
这里由BeanNameUrlHandlerMapping
类来实现。
从上面的源码分析我们可以得知,在 SpringMVC 容器中,且在注入了 BeanNameUrlHandlerMapping
映射器的时候,只要是以 "/" 开头的 bean 的 name,都会作为该映射器匹配的 Handler
对象。
这种模式对应的是继承AbstractController类来开发自己业务的场景。现在很少了,现在比较多的是映射方法,因为映射类的话,一个类里只能处理一个请求。
2.1.2 SimpleUrlHandlerMapping
在接下来咱们看看该映射器是怎么调用父类的 registerHandler(String urlPath, Object handler)
方法将 handler
加进 AbstractUrlHandlerMapping
的 this.handlerMap
中。
从上面源码可以看出 SimpleUrlHandlerMapping
映射器跟前面 BeanNameUrlHandlerMapping
映射器有点不一样。后者是有点类似遍历容器里面有所的 bean
的 name
或 id
找到匹配的,并且 bean
的 name
或 id
有特殊要求,匹配的则加入。而前者则是先将加入该映射器的 handler
先加进该映射器的一个集合属性里面,容器初始化的时候免去了遍历麻烦的步骤。
2.2 AbstractHandlerMapping
实现类分支之二 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping
最终获取的 handler
是 HandlerMethod
类型对象。
具体实现类只有一个RequestMappingHandlerMapping,这中模式也就是我们现在比较常用的使用 @Controller
和 @RequestMapping
这样注解来描述视图控制器的逻辑。
2.2.1 简单了解 HandlerMethod
HandlerMethod
其实可以简单理解为保持方法信息的pojo类
2.2.2 RequestMappingInfo
类
主要用来记录方法上 @RequestMapping()
注解里面的参数,针对 RequestMappingHandlerMapping
映射器来使用。
在容器初始化过程中创建映射器(RequestMappingHandlerMapping
)对象时,会寻找所有被@Controller
注解类中被 @RequestMapping
注解的方法,然后解析方法上的 @RequestMapping
注解,把解析结果封装成 RequestMappingInfo
对象,也就是说RequestMappingInfo
对象是用来装载方法的匹配相关信息,每个匹配的方法都会对应一个 RequestMappingInfo
对象。现在大家应该能明白 RequestMappingInfo
的作用了吧。
PatternsRequestCondition
:模式请求路径过滤器,对应记录和判断@RequestMapping
注解上的value
属性。RequestMethodsRequestCondition
:请求方法过滤器,对应记录和判断@RequestMapping
注解上的method
属性。ParamsRequestCondition
:请求参数过滤器,对应记录和判断@RequestMapping
注解上的params
属性。HeadersRequestCondition
:头字段过滤器,对应记录和判断@RequestMapping
注解上的headers
属性。ConsumesRequestCondition
:请求媒体类型过滤器,对应记录和判断@RequestMapping
注解上的consumes
属性。ProducesRequestCondition
:应答媒体类型过滤器,对应记录和判断@RequestMapping
注解上的produces
属性。RequestConditionHolder
:预留自定义扩展过滤器。
2.2.3 进入 AbstractHandlerMethodMapping
映射器内部
① 首先来看下该类实现父抽象类(AbstractHandlerMapping
) 的抽象方法 getHandlerInternal(..)
匹配并返回对应的 handler
对象。
跟前面的另一个实现分支 AbstractUrlHandlerMapping
实现看起来差不多,都是根据请求路径来匹对,但是内部配对方式有什么不同还需要我们接着往下看。
注意:
Match
就是该抽象类里面自定义的一个内部类,用来记录方法标记信息对象mapping
和方法源信息对象HandlerMethod
。- 当请求为 restful 风格时,将会遍历所有的 mapping,然后一个个匹对,非常耗时和费资源。优化请参考 springMVC在restful风格的性能优化
- 上面的两个抽象方法(
getMatchingMapping(..)
和getMappingComparator(..)
)
前者要实现检查提供的请求映射信息中的条件是否与请求匹配。
后者要实现当一个Request
对应多个mapping
时的择优方案。
② 看下存储映射关系对象(MappingRegistry
)内部结构
说到这里,可能大家对于这个 this.mappingRegistery
对象十分好奇,里面到底是怎么存储数据的,先是可以根据 lookupPath
找到 List<mapping>
,接着后来又根据 mapping
找到 HandlerMethod
对象。
该实体类里面最重要的两个记录集合分别是 mappingLookup
和 urlLookup
。
urlLookup
:主要用来记录lookupPath
请求路径对应的mapping
集合。
这里 Spring 留了一个很活的机制,拿@RequestMapping
注解来说,他的value
属性本身就是一个字符数组,在多重设置中难免有路径重复的,所以最终有可能会出现一个lookupPath
对应多个RequestMappingInfo
,最终在请求过来的时候给了自定义抽象方法让实现类自己实现择优的方式。MutivalueMap
是 SpringMVC 自定义的一个Map
类,key 对应的 value 是一个集合,这从名字上也能看出来。mappingLookup
:key 是mapping
对象,value 是HandlerMethod
对象,最终是通过lookupPath
在urlLookup
集合中找到对应的mapping
对象,通过mapping
在mappingLookup
集合中找到HandlerMethod
对象。
③ 看下是怎么将映射关系装进缓存(MappingRegistry
) 对象中的
容器初始化的时候都干了些什么isHandler(..)
是该抽象类定义的抽象方法,由实现类自己去实现匹对哪些类。看下 RequestMappingHandlerMapping
映射器是怎么实现的吧
看来 RequestMappingHandlerMapping
映射器,只要类上有 Controller
或 RequestMapping
注解,就符合该映射器管辖范围。
来个分支看下 RequestMappingHandlerMapping
是怎么实现抽象方法 getMappingForMethod(..)
方法的,该映射器都匹配什么样的方法呢?
猜也能猜到,RequestMappingHandlerMapping
映射器肯定匹配有 @RequestMapping
注解的方法,并返回该方法的映射信息对象 RequestMappingInfo
对象。
下面就到了最后一步,具体这个映射关系是怎么装入映射器的 MappingRegistry
对象属性的缓存的呢?
三、 总结
到这里,关于 SpringMVC 内部是怎么通过 HandlerMapping
映射器将各自对应映射的资源在容器初始的时候装到自身的缓存,在请求过来时又是怎么找到对应的资源返回最终对应的 handler
对象已经描述完了。
现在开发我们基本都不用 AbstractUrlHandlerMapping
这种类型的映射器了,但是 SpringMVC 内部还有用到的地方,例如直接 <mvc:view-controller path="" view-name=""/>
标签配置资源不经过视图控制器直接跳转就用到了 SimpleUrlHandlerMapping
这种映射器。AbstractUrlHandlerMapping
匹对解析对应请求最终返回的 handler
是 Controller
对象。
现在我们习惯直接用 @Controller
和 @RequestMapping
这样注解来描述视图控制器的逻辑,这种资源映射用的是 AbstractHandlerMethodMapping
抽象类的子类 RequestMappingHandlerMapping
映射器,匹对解析对应的请求返回HandlerMethod
对象。