【老王读SpringMVC-2】url 与 controller method 的映射关系注册

news2024/10/6 1:45:49

上文提到,如果我们自己要实现 spring mvc 框架的话,大致需要实现如下功能:

  • 0、将 url 与 Controller method 的对应关系进行注册
  • 1、通过请求的 url 找到 Controller method (即 url 与 Controller method 的映射)
  • 2、将请求参数进行绑定,即将入参绑定到 Controller method 的参数对象上
  • 3、执行处 Controller method (即 HandlerAdapter#handle())
  • 4、对 Controller method 的返回值进行处理
    4.1 如果正常返回的话,对返回值对象进行处理(即 ReturnValueHandler)
    包括:如果返回视图 View 的话,对视图进行渲染 (即 ViewResolver)
    4.2 如果有异常返回的话,对异常进行处理(即 @ExceptionHandler)

下面我们就来研究一下,Spring MVC是如何将 url 与 controller method 的映射关系找出来进行注册的?

分析 url 与 handler method 的映射关系的注册

经过前面对 DispatcherServlet#doDispatch() 的分析,我们知道断点应该打在获取 HandlerExecutionChain 的地方。

/**
 * 将所有的 HandlerMapping 按顺序遍历一次,获取 request 对应的 HandlerExecutionChain。
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

可以看到,在获取 HandlerExecutionChain 时,Spring 会将所有的 HandlerMapping 按顺序遍历一次。
而在 Spring 中有许多 HandlerMapping,最常用的当属 RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerMapping

HandlerMapping 是用来定义 request 和处理程序(handler)之间的映射关系的。

Spring 内置了两个常用的实现: BeanNameUrlHandlerMappingRequestMappingHandlerMapping
用户可以编写自定义的 HandlerMapping,并通过实现 org.springframework.core.Ordered 来指定优先级。

下面我们来看下 HandlerMapping 的类图:

HandlerMapping.png

SpringBoot 默认注册的 HandlerMapping 有:

  • RequestMappingHandlerMapping – 处理 @RequestMapping 注解的 url 与处理程序的映射 (最常用)
  • BeanNameUrlHandlerMapping – 将 / 开头的 beanName 与 url 进行映射
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping – 处理普通的 url
  • WelcomePageHandlerMapping – 处理首页

RequestMappingHandlerMapping 注册映射关系

在 SpringMVC 中,我们最常用的定义端点接口的方式是使用 @RequestMapping
所以,我们主要来研究一下注解形式的 url 是如何进行映射关系注册的?

在 SpringMVC 中,RequestMappingHandlerMapping 是用来支持 @RequestMapping 注解形式的 url 的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping 中完成的:

registerHandlerMethod.png

可以看到,当 RequestMappingHandlerMapping 这个 bean 在加载的时候,会调用父类的 AbstractHandlerMethodMapping#afterPropertiesSet() 来完成 url 与 handler method 映射关系的注册。
具体的注册是由 AbstractHandlerMethodMapping.MappingRegistry#registry() 来完成的。

判断哪些类是 handler:RequestMappingHandlerMapping#isHandler()

/**
 * bean class 上如果有 @Controller 或 @RequestMapping 的话,就认为是一个 handler 处理程序  
 */
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

具体的 register 注册逻辑

request 和 handler method 映射关系的注册是由 AbstractHandlerMethodMapping.MappingRegistry 来实现的。

MappingRegistry 的定义如下:

class MappingRegistry {
    
    // 保存所有的 <RequestMappingInfo, MappingRegistration>  
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
    
    // 保存 RequestMappingInfo 中拥有 directPath 的: <directPath, RequestMappingInfo>
    private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
    
    // 保存所有的 HandlerMethod name: <name, List<HandlerMethod>>
    private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
    
    // 保存拥有 cors 配置的 HandlerMethod: <HandlerMethod, CorsConfiguration>
    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
    .....
}

AbstractHandlerMethodMapping.MappingRegistry#register() 是负责具体的注册逻辑的:
register.png

可以看到,这里注册了 4 种信息:
1、将 directPath 注册到 pathLookup 中
2、将 HandlerMethod 的 name 注册到 nameLookup 中
3、将 HandlerMethod 注册到 corsLookup 中(处理跨域请求映射)
4、将所有的 RequestMappingInfo 全部都注册到 registry 中

directPath: 指那些没有特殊字符的 path。特殊字符:?、*、{}

补充:SpringBoot 中 RequestMappingHandlerMapping bean 是在哪里定义的?
WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping() 中定义的:

// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping
@Bean
@Primary
public RequestMappingHandlerMapping requestMappingHandlerMapping(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    // Must be @Primary for MvcUriComponentsBuilder to work
    return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
            resourceUrlProvider);
}

找到 bean 定义处的技巧:
断点打在 bean class 的构造函数或初始化方法里面,当断点进入时,可以很方便的从调用堆栈中找到相应的 BeanDefinition 的值,BeanDefinition 中就记录了这个 bean 是从在哪里定义的。
如果断点打不到 bean class 里面的话,那么就可以在 applicationContext 中获取相应的 BeanDefinition,再查看 bean 定义的地方。
核心就是要找到 bean 对应的 BeanDefinition。

小结

在 SpringMVC 中,request 与 handler method 的请求关系注册和映射都是通过 HandlerMapping 来完成的。
HandlerMapping 的实现类中最常用的是 RequestMappingHandlerMapping,它是用来处理 @RequestMapping 注解形式的请求关系映射的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping#registerHandlerMethod() 中完成的。
这里注册了 4 种信息:
1、将 directPath 注册到 pathLookup 中
2、将 HandlerMethod 的 name 注册到 nameLookup 中
3、将 HandlerMethod 注册到 corsLookup 中(处理跨域请求映射)
4、将所有的 RequestMappingInfo 全部都注册到 registry 中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461866.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python中的迭代器了解一下?】

基本说明 在 Python 中&#xff0c;迭代器是一种用于遍历可迭代对象&#xff08;如列表、元组、字符串等&#xff09;的方式。迭代器提供了一种简洁而有效的方法来遍历序列&#xff0c;而不需要创建临时变量或使用循环语句。 在 Python 中&#xff0c;迭代器是一个实现了 __i…

没有U盘电脑如何使用本地硬盘安装Ubuntu20.04(双系统)

环境: DELL7080台式机 Ubuntu20.04 两块硬盘 问题描述: 没有U盘电脑如何使用本地硬盘安装Ubuntu20.04(双系统) 解决方案: 一、下载镜像文件 1.上线自行下载安装镜像文件 二、分区 1.win10下磁盘管理压缩2个分区一个10G左右制作安装盘,一个几百G安装系统使用 10…

【Android入门到项目实战-- 7.1】—— 如何使用通知?

目录 一、创建通知的步骤 1、创建一个NotificationManager实例 2、使用一个Builder构造器来创建Notification对象 3、设置标题、文字、时间和图标等信息 4、显示通知 二、通知实例演示 三、实现通知的点击效果 1、PendingIntent 什么是PendingIntent&#xff1f; 如何使…

后台-husky提交代码规范使用

husky是一个git hook工具&#xff0c;可以帮助我们触发git提交的各个阶段&#xff1a;pre-commit、commit-msg、pre-push 1.如何使用husky呢&#xff1f; npx husky-init && npm installWindows安装不成功试试npx husky-init && npm install 2.git commit规范…

线索二叉树的前序遍历

线索二叉树原理 遍历二叉树的其实就是以一定规则将二叉树中的结点排列成一个线性序列&#xff0c;得到二叉树中结点的先序序列、中序序列或后序序列。这些线性序列中的每一个元素都有且仅有一个前驱结点和后继结点。 但是当我们希望得到二叉树中某一个结点的前驱或者后继结点时…

计算机组成原理---第 6 章总线系统

一、总线的概念和结构形态 总线的基本概念 ⑴概述 总线是构成计算机系统的互联机构&#xff0c;是多个系统功能部件之间进行数据传送的公共通路。 ⑵ 分类 总线的分类方式有很多&#xff1a;如被分为外部总线和内部总线、系统总线和非系统总线、片内总线和PCB板级总线、串行…

VS2022+opengl环境配置

glfw下载Download | GLFW glad下载https://glad.dav1d.de/ Freeglut下载 https://freeglut.sourceforge.net/index.php#download cmake下载 Download | CMake glfwFreeglut 用cmake配置Freeglut&#xff0c;生成vs工程项目&#xff0c;用vs2022编译项目&#xff0c;生成fr…

27.Spring的事务控制

目录 一、编程式事务控制相关对象。 &#xff08;1&#xff09;事务管理器。 &#xff08;2&#xff09;事务定义信息对象&#xff08;如隔离级别、传播行为&#xff09;。 &#xff08;3&#xff09;事务状态对象。 &#xff08;4&#xff09; 知识要点。 二、声明式事务…

基于STM32的开源简易示波器项目

目录 ​一、前言 二、硬件接线 三、信号的采集 四、代码配置 五、数据的处理 六、模拟正弦波输出 七、模拟噪声或三角波输出 八、显示函数与按键控制 ​一、前言 该项目是基于正点原子精英板制作的一个简易示波器&#xff0c;可以读取信号的频率和幅值&#xff0c;并可…

JetpackCompose从入门到实战学习笔记14

JetpackCompose从入门到实战学习笔记14——Coli的简单使用 1.简介&#xff1a; Coil 是一个 Android官方出的配合Jetpack的图片加载库&#xff0c;通过 Kotlin 协程的方式加载图片。 优点如下&#xff1a; 更快: Coil 在性能上有很多优化&#xff0c;包括内存缓存和磁盘缓存…

C/C++基础知识

专栏&#xff1a;C/C 个人主页&#xff1a; C/C基础知识 前言C关键字(C98)命名空间命名空间的定义正常的命名空间的定义如何使用命名空间 命名空间可以嵌套同一个工程中允许存在多个相同名称的命名空间&#xff0c;编译器最后会合成同一个命名空间中(一个工程中的.h文件和test.…

(数字图像处理MATLAB+Python)第七章图像锐化-第一、二节:图像锐化概述和微分算子

文章目录 一&#xff1a;图像边缘分析二&#xff1a;一阶微分算子&#xff08;1&#xff09;梯度算子A&#xff1a;定义B&#xff1a;边缘检测C&#xff1a;示例D&#xff1a;程序 &#xff08;2&#xff09;Robert算子A&#xff1a;定义B&#xff1a;示例C&#xff1a;程序 &a…

Tailscale: Please Restart the Tailscale Windows Service

之前用的好好的&#xff0c;最近重新升级了一下Tailscale后发现一直连不上。右击win10右下角的Tailscale图标&#xff0c;第一行显示&#xff1a;Please Restart the Tailscale Windows Service。 我查看了一下服务&#xff0c;发现Tailscale是自动的&#xff0c;这里的启动类…

vuex存储数组(新建,增,删,更新),并存入localstorage定时删除

vuex存储数组(新建&#xff0c;增&#xff0c;删&#xff0c;更新)&#xff0c;并存入localstorage定时删除 本文目录 vuex存储数组(新建&#xff0c;增&#xff0c;删&#xff0c;更新)&#xff0c;并存入localstorage定时删除使用背景store中实现增删改组件中维护数组&#x…

缩小数据文件

今天又出现12.2c 环境的问题&#xff0c;1T的数据空间还剩下2G&#xff0c;吓了一身冷汗&#xff0c;赶紧查看原因&#xff0c;不知道哪路业务大神作妖了。 发现sysaux和system增加N多数据文件&#xff0c;而且目前使用不多&#xff0c; 缩小表空间的数据文件 可以使用下面的语…

直升机空气动力学基础---002 桨叶的主要参数

源于 1.桨叶的平面形状和主要参数 由于其设计制造比较简单&#xff0c;早期直升机大多采用矩形桨叶&#xff0c;缺点是在高速气流中&#xff0c;无法抑制桨尖涡&#xff0c;会消耗向下的诱导速度&#xff0c;降低旋翼的拉力。现代多采用梯形桨叶。 桨尖后掠能够降低桨尖涡 …

【Linux】Linux基本指令(2)

一.你如何看待指令 指令说白了就是可执行程序&#xff0c;且指令一定是在系统的某一个位置存在的&#xff0c;在执行指令前&#xff0c;我们需要先找到它。 二.man指令 众所周知&#xff0c;Linux的指令有很多&#xff0c;指令的选项也有很多&#xff0c;我们不可能全记住&…

android注解注入AspectJ面向切面AOP插桩技术改变android原生类对象行为记录View点击事件,Java(3)

droid注解注入AspectJ面向切面AOP插桩技术改变android原生类对象行为记录View点击事件&#xff0c;Java&#xff08;3&#xff09; 动态改变Toast提示的内容&#xff0c;弹之前修改。Button在每次点击后记录。 import android.util.Log; import android.widget.Toast;import o…

基于天牛须(BAS)与NSGA-Ⅱ混合算法的交直流混合微电网多场景多目标优化调度(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

论Shell编程规范与变量

目录 一&#xff1a;shell脚本 1.shell概述 2.linux中包含的常用shell 3.shell脚本概述 4.shell脚本应用场景 5.shell脚本的作用 6.用户的登录shell 二&#xff1a; 编写脚本 1.脚本的基本格式 2.shell脚本的执行 3. 交互式硬件设备 4.重定向操作 5.管道操作符号 “…