该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.2.4.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》

HandlerMapping 组件

HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors

  • handler 处理器是 Object 类型,可以将其理解成 HandlerMethod 对象(例如我们使用最多的 @RequestMapping 注解所标注的方法会解析成该对象),包含了方法的所有信息,通过该对象能够执行该方法

  • HandlerInterceptor 拦截器对处理请求进行增强处理,可用于在执行方法前、成功执行方法后、处理完成后进行一些逻辑处理

由于 HandlerMapping 组件涉及到的内容比较多,考虑到内容的排版,所以将这部分内容拆分成了四个模块,依次进行分析:

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,依次注册处理器

  1. 如果非延迟加载,并且 handler 为 String 类型,并且还是单例,则去获取 String 对应的 Bean 对象,resolvedHandler
  2. handlerMap中获得 urlPath 对应的处理器
  3. 如果该路径已存在对应的处理器,但是不是当前 resolvedHandler 对象,则抛出异常
  4. 否则,该路径不存在对应的处理器,则将当前 resolvedHandler 处理器保存
    1. 如果是 / 根路径,则设置 resolvedHandlerrootHandler
    2. 否则,如果是 /* 路径,则设置为默认处理器 defaultHandler(在父类中)
    3. 否则,添加到 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;
}
  1. 获得请求路径

  2. 调用 lookupHandler(String urlPath, HttpServletRequest request) 方法,获得处理器,详情见下文

  3. 如果找不到处理器,则使用 rootHandlerdefaultHandler 处理器

    1. 如果是/根路径,则使用 rootHandler 处理器
    2. 否则,使用 defaultHandler 默认处理器
    3. 如果找到的处理器是 String 类型,则从容器中找到该 beanName 对应的 Bean 作为处理器
    4. 调用validateHandler(Object handler, HttpServletRequest request),对处理器进行校验,空方法,暂无子类实现该方法
    5. 调用 buildPathExposingHandler方法,创建 HandlerExecutionChain 处理器执行链,赋值给handler处理器,详情见下文
  4. 返回请求对应的handler处理器

所以说这里但会的处理器对象可能是一个 HandlerExecutionChain 对象,用途目前不清楚

精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping的更多相关文章

  1. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  2. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. 新鲜出炉!阿里巴巴,京东,美团面经汇总,已拿offer!

    最近在后台收到了很多小伙伴的私信,说自己最近想好好准备一下,在不久后的秋招跳个槽往大厂冲击一下,想要我给大家整理出一份大厂面试题好用来做好准备.之前公司有点事比较忙就没回私信.最近好一点了我也是立马联 ...

  2. CorelDRAW文件损坏的几种解决方法

    以前做好的CorelDRAW文件突然打不开了,或者是死机.非法操作等原因造成CorelDRAW文件损坏,有时打开源文件发现一片空白,源文件保存损坏无法打开怎么办?此时不要着急,你可以试试以下几种办法帮 ...

  3. guitar pro系列教程(十五):Guitar Pro播放没有声音怎么办?

    相信很多朋友在安装了guitar pro打谱后都会发现没有声音,关于这个问题相信大家都很苦恼到底是什么问题呢,今天小编就这个问题与大家好好讨论讨论,希望对大家有帮助,感兴趣的朋友可以进来一起学习哦. ...

  4. ubuntu安装php的 mongodb扩展

    wget https://pecl.php.net/get/mongodb-1.2.6.tgztar -zxvf mongodb-1.2.6.tgz cd mongodb-1.2.6 /usr/bin ...

  5. synchronized关键字的可重入性

    /**父类*/public class SynchronizedDemo1 implements Runnable { @Override public void run() { try { meth ...

  6. 数学分析理论(rudin版)笔记:实数系和复数系.1

    导引 有理数集是"稀疏的"和"稠密的". 选择公理 考虑以下问题:容易找到两个无理数 a, b 使 a + b 为有理数,或者使 ab 为有理数,但是能否使得 ...

  7. git原理-本地仓库认识

    项目人员使用git,几乎70%的工作都是在本地仓库完成的.由此可见本地仓库的重要性. 下面我们就通过一些基本的命令讲下git的本地仓库的结构,存储流程,数据类型,如何存储...... 仓库结构 大家都 ...

  8. JUC详解--【Foam番茄】

    1.什么是JUC java.util 工具包 业务:普通的线程代码 Thread Runnable 没有返回值,效率相比于 Callable 相对较低! 2.线程和进程 进程:一个程序,QQ.exe ...

  9. 七牛云实现前端js上传实现办法

    1.七牛云上传前台页面 1.1 安装相关包 npm install --save jquery@1.12.1 # 安装jquery 1.2 index.html 引入qiniu.min.js < ...

  10. PyQt(Python+Qt)学习随笔:QDockWidget停靠部件floating和features属性

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 1.floating属性 floating属性表示QDockWidge ...