精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读
Spring 版本:5.2.4.RELEASE
该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》
HandlerMapping 组件
HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)
handler处理器是 Object 类型,可以将其理解成 HandlerMethod 对象(例如我们使用最多的@RequestMapping注解所标注的方法会解析成该对象),包含了方法的所有信息,通过该对象能够执行该方法HandlerInterceptor拦截器对处理请求进行增强处理,可用于在执行方法前、成功执行方法后、处理完成后进行一些逻辑处理
由于 HandlerMapping 组件涉及到的内容比较多,考虑到内容的排版,所以将这部分内容拆分成了四个模块,依次进行分析:
- 《HandlerMapping 组件(一)之 AbstractHandlerMapping》
- 《HandlerMapping 组件(二)之 HandlerInterceptor 拦截器》
- 《HandlerMapping 组件(三)之 AbstractHandlerMethodMapping》
- 《HandlerMapping 组件(四)之 AbstractUrlHandlerMapping》
HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
先来回顾一下HandlerMapping 接口体系的结构:

在《HandlerMapping 组件(一)之 AbstractHandlerMapping》文档中已经分析了 HandlerMapping 组件的 AbstractHandlerMapping 抽象类基类
那么本文就接着来分析图中黄色框部分的 AbstractUrlHandlerMapping 系,基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被 @RequestMapping 等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式。
因为 AbstractUrlHandlerMapping 在实际开发基本不会涉及到,所以本文选读,可以直接查看总结部分
一共有五个子类,分成两条线:
- AbstractUrlHandlerMapping <= SimpleUrlHandlerMapping <= WebSocketHandlerMapping
- AbstractUrlHandlerMapping <= AbstractDetectingUrlHandlerMapping <= BeanNameUrlHandlerMapping
其中,WebSocketHandlerMapping 是 spring-websocket 项目中的类,本文会无视它
所以,本文按照 AbstractUrlHandlerMapping、SimpleUrlHandlerMapping、AbstractDetectingUrlHandlerMapping、BeanNameUrlHandlerMapping 顺序进行分析
回顾
先来回顾一下在 DispatcherServlet 中处理请求的过程中通过 HandlerMapping 组件,获取到 HandlerExecutionChain 处理器执行链的方法,是通过AbstractHandlerMapping 的 getHandler 方法来获取的,如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// <1> 获得处理器(HandlerMethod 或者 HandlerExecutionChain),该方法是抽象方法,由子类实现
Object handler = getHandlerInternal(request);
// <2> 获得不到,则使用默认处理器
// <3> 还是获得不到,则返回 null
// <4> 如果找到的处理器是 String 类型,则从 Spring 容器中找到对应的 Bean 作为处理器
// <5> 创建 HandlerExecutionChain 对象(包含处理器和拦截器)
// ... 省略相关代码
return executionChain;
}
在 AbstractHandlerMapping 获取 HandlerExecutionChain 处理器执行链的方法中,需要先调用 getHandlerInternal(HttpServletRequest request) 抽象方法,获取请求对应的处理器,该方法由子类去实现,也就上图中黄色框和红色框两类子类,本文分析黄色框部分内容
AbstractUrlHandlerMapping
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping,实现 MatchableHandlerMapping 接口,继承 AbstractHandlerMapping 抽象类,以 URL 作为 Handler 处理器 的 HandlerMapping 抽象类,提供 Handler 的获取、注册等等通用的骨架方法。
构造方法
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
/**
* 根路径("/")的处理器
*/
@Nullable
private Object rootHandler;
/**
* 使用后置的 / 匹配
*/
private boolean useTrailingSlashMatch = false;
/**
* 是否延迟加载处理器,默认关闭
*/
private boolean lazyInitHandlers = false;
/**
* 路径和处理器的映射
*
* KEY:路径 {@link #lookupHandler(String, HttpServletRequest)}
*/
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
}
registerHandler
registerHandler(String[] urlPaths, String beanName) 方法,注册多个 URL 的处理器,方法如下:
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
// <1> 如果非延迟加载,并且 handler 为 String 类型,并且还是单例,则去获取 String 对应的 Bean 对象
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
// <2> 获得 urlPath 对应的处理器
Object mappedHandler = this.handlerMap.get(urlPath);
// <3> 检验 mappedHandler 是否已存在,如果已存在,并且不是当前 resolvedHandler 对象,则抛出异常
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
// <4.1> 如果是 / 根路径,则设置为 rootHandler
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
// <4.2> 如果是 /* 路径,则设置为默认处理器
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
// <4.3> 添加到 handlerMap 中
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
遍历 URL,依次注册处理器
- 如果非延迟加载,并且
handler为 String 类型,并且还是单例,则去获取 String 对应的 Bean 对象,resolvedHandler - 从
handlerMap中获得urlPath对应的处理器 - 如果该路径已存在对应的处理器,但是不是当前
resolvedHandler对象,则抛出异常 - 否则,该路径不存在对应的处理器,则将当前
resolvedHandler处理器保存- 如果是
/根路径,则设置resolvedHandler为rootHandler - 否则,如果是
/*路径,则设置为默认处理器defaultHandler(在父类中) - 否则,添加到
handlerMap中
- 如果是
getHandlerInternal
实现父类的 getHandlerInternal(HttpServletRequest request) 方法,获得处理器,方法如下:
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// <1> 获得请求的路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// <2> 获得处理器
Object handler = lookupHandler(lookupPath, request);
// <3> 如果找不到处理器,则使用 rootHandler 或 defaultHandler 处理器
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
// <3.1> 如果是根路径,则使用 rootHandler 处理器
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
// <3.2> 使用默认处理器
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
// <3.3> 如果找到的处理器是 String 类型,则从容器中找到该 beanName 对应的 Bean 作为处理器
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
// <3.4> 空方法,校验处理器。目前暂无子类实现该方法
validateHandler(rawHandler, request);
// <3.5> 创建处理器(HandlerExecutionChain 对象)
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
获得请求路径
调用
lookupHandler(String urlPath, HttpServletRequest request)方法,获得处理器,详情见下文如果找不到处理器,则使用
rootHandler或defaultHandler处理器- 如果是
/根路径,则使用rootHandler处理器 - 否则,使用
defaultHandler默认处理器 - 如果找到的处理器是 String 类型,则从容器中找到该 beanName 对应的 Bean 作为处理器
- 调用
validateHandler(Object handler, HttpServletRequest request),对处理器进行校验,空方法,暂无子类实现该方法 - 调用
buildPathExposingHandler方法,创建 HandlerExecutionChain 处理器执行链,赋值给handler处理器,详情见下文
- 如果是
返回请求对应的
handler处理器
所以说这里但会的处理器对象可能是一个 HandlerExecutionChain 对象,用途目前不清楚
精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping的更多相关文章
- 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - MultipartResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
随机推荐
- tp5配置引入使用redis
1.首先你的php得是已经安装了redis扩展的 2.在tp里找到config.php配置文件,找到cache,改成下面的样子 'cache' => [ // 选择模式 'type' => ...
- get_object_vars
get_object_vars()
- MathType总结编辑括号的类型(中)
MathType中的符号模板多种多样,能够满足各类用户的使用需求.即使是一些不常用的符号也有相应的模板,因为在工作学习中我们都需要他们,只是有的用的多有的用的少而已,下面我们就一起来看看公式编辑器编辑 ...
- FL Studio钢琴卷轴之工具菜单的Riff命令
鼠标左键点击FL Studio钢琴卷轴窗口中的"工具"命令,我们就可以打开快捷工具菜单.快捷菜单中包含了用于音符编辑的各种工具.按照该菜单的顺序,我们先来看一下什么是Riff器命令 ...
- FL Slayer合成器功能之顶部组件介绍
本章节采用图文结合的方式给大家介绍电音编曲软件--FL Studio中的插件FL Slayer合成器中的顶部组件,它是电吉他模拟合成器,感兴趣的朋友可以一起沟通学习交流. FL Slayer(杀手)合 ...
- 刚安装好的MathType怎么使用
对于刚接触公式编辑器的新手来说,难免会存在很多疑问:如何使用刚安装好的Word公式编辑器?安装好公式编辑器之后,我们在哪里找到这个工具呢?下面就针对大家的这些疑问,来给大家介绍下首次使用MathTyp ...
- 使用Camtasia来给视频或者图片调色
喜欢摄影和制作视频的朋友可能知道,一张好看的照片或一段精美视频的构成要素很多,取景本身就是很重要的条件,相机的硬件水平也是一个重要因素,接下来的就是后期的编辑和处理了,而在后期处理过程中调色是十分重要 ...
- 洛谷 P1284 三角形牧场 题解(背包+海伦公式)
题目链接 题目大意 给你 n块木板(n<=40),每块木板长度为\(l[i]<=40\) 每块木板都要用,求最大的三角形面积×100,答案直接舍去小数 题目思路 首先如果已知三条边的长度可 ...
- 用微信表情翻译表白,程序员的小浪漫,赶紧Get起来!
- JZOJ8月5日提高组反思
JZOJ8月5日提高组反思 再次炸了 虽然不是爆0 但也没差多少-- T1 想的DP 然后就打了 一开始是只能拿60的 后来想到了用前缀和优化 然后打完交了 最后一分钟测了一下空间 爆了 就赶紧把数组 ...