该系列文档是本人在学习 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. Apache Shiro 反序列化漏洞复现(CVE-2016-4437)

    漏洞描述 Apache Shiro是一个Java安全框架,执行身份验证.授权.密码和会话管理.只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞. 漏洞原理 A ...

  2. 批量反编译.class

    使用dj java Decompiler软件,安装后,安装目录会有个jad.exe程序 控制台执行: jad -o -r -dF:\output_dir -sjava F:\class_root_di ...

  3. 微软开源的 AI 工具,让旧照片焕发新生

    原文地址:Bringing Old Photos Back to Life 原文作者:Ziyu Wan 译者 & 校正:HelloGitHub-小鱼干 & 鸭鸭 写在前面 在 GitH ...

  4. pip更新报错问题

    pip更新错误如下: WARNING: You are using pip version 20.1.1; however, version 20.2 is available. You should ...

  5. python3 Redis未授权检测脚本

    `import sys import getopt import socket def get_target(): opts, args = getopt.getopt(sys.argv[1:], ' ...

  6. 1.Cobaltstrike 安装与简介

    1.Cobaltstrike 安装与简介 一.简介 Cobalt Strike是一款美国Red Team开发的渗透测试神器,常被业界人内称为CS.自去年起, Cobaltstrike升级到3.0版本, ...

  7. 企业安全01-Apache solr XML实体注入漏洞CVE-2017-12629

    Apache solr XML 实体注入漏洞CVE-2017-12629 一.简介 Apache Solr 是一个开源的搜索服务器.Solr 使用 Java 语言开发,主要基于 HTTP 和 Apac ...

  8. 心跳event

    在通过sessionID和passwd获取会话的时候会使原会话断开,后续的事件都变为disconnected,且zk会不断发送disconnected给原连接,connected给新连接

  9. Appium上下文和H5测试(二)

    坚持原创输出,点击蓝字关注我吧 作者:清菡 博客:oschina.云+社区.知乎等各大平台都有. 文章总览图 一.往期回顾 loc='new UiSelector().text("全程班&q ...

  10. 使用wapiti进网站进行安全性测试

    1.安装wapiti --在命令终端输入 pip install wapiti3 (因为这个结合python使用,所以安装的版本要跟python兼容,因为我的python是3.6版本,所以安装的是wa ...