@RequestMapping是怎么处理映射的?

在享受了@RequestMapping方便的处理映射时,忍不住会开始好奇,

  • 这个注解怎么就能找到我的接口?
  • @RequestMapping到底替我做了什么事?

今天来解答如上两个问题。

画了个比较难看的图,不过都是老生常谈了,逻辑都是熟悉的。
在这里插入图片描述
SpringMVC在处理一个请求时,会首先通过请求头中的Request URL中的信息,查找Spring的映射处理器,顾名思义,既然是映射,那么一定存在一个键值对,存在一一对应的关系。

那我来找一下这个映射保存在哪里了。

在这里插入图片描述
由于是学习DispatcherServlet,就直接写个简单的SpringBoot程序,打上断点,在idea的debug模式中,查看调用链,或者叫有的人称呼为栈帧。
在这里明显发现,在进入接口前(从上往下观察调用链),经历了好几次的反射,然后是RequestMappingHandlerAdapter(这个类比较重要等一下聊),再然后是DispatcherServlet。

马上点进DispatcherServlet,发现一行亲切的注释。

在这里插入图片描述

原来这里就是实际调用处理程序的地方。

检查一下变量,发现这个处理程序就是我的接口,证明映射已经找好了。

在这里插入图片描述

我今天要查的是@RequestMapping是怎么找到我接口的,所以往上翻,查查这个mappedHandler到底是怎么来的。

在这里插入图片描述
翻了几行就看到了,而且也有一行亲切的注释。

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

确定当前请求的处理程序。
很明显,就它了。继续点进去看。

在这里插入图片描述
这是一条比较长的调用链,中间过程就跳过了,直接看结果。
urlPath就是从Request Header中提取的URL。
pathLookup则是我这个程序中的全部接口的路径。
可以发现这个就是最开始的图中,处理映射的关键部分了。
SpringMVC通过维护pathLookup这个Map来处理URL与接口的映射关系。

在这里插入图片描述
但是又有一个问题,这不是一对一呀。MultiValueMap明显是一个key对应几个value的结构。
写过bug的都知道,一个程序中,@RequestMapping写重复了是起不来项目的。
在这里插入图片描述

为什么SpringMVC要选择这种结构呢?等下面再分析。

解决了@RequestMapping是怎么找到我的接口的问题了
现在想知道,@RequestMapping为了能找到我的接口,背地里都做了些什么

为了了解pathLookup的初始化过程,就必须要了解SpringMVC的加载过程。
在这里插入图片描述
还是看AbstractHandlerMethodMapping,这里发现是一个循环中添加的。
而且在register方法632行,居然还加了读写锁的写锁,什么情况,难道这里还有并发问题?
先留下问题继续Alt+F7查上游调用链。
(这里补充一点,Alt+F7不好使多半是GeForce Experience的游戏内覆盖模块占用了快捷键,建议改一下或者干脆关掉)
在register内打上断点,重启项目,发现进断点了,看一下调用链。
在这里插入图片描述
又引出一个类,RequestMappingHandlerMapping。
跟刚才关注的RequestMappingHandlerAdapter长得可以说是如同亲兄弟。
由于个人水平有限和篇幅原因,这两兄弟只能在下篇文章中聊一下个人的简单认识。

从上图中的调用链可以看出,最终调用是在afterPropertiesSet。
这是一个InitializingBean接口中的方法,实现了InitializingBean的类必须重写afterPropertiesSet,而afterPropertiesSet将会在类实例化以后立刻调用。

而在AbstractHandlerMethodMapping的afterPropertiesSet中调用的是initHandlerMethods。
在这里插入图片描述
注释说的很明白,这里做两件事,第一件事是扫描所有的bean,第二件事是注册处理程序,也就是刚刚聊的建立映射关系。
通过ApplicationContext获取到所有Object类的子类,可以看到包含了程序中的controller。
在这里插入图片描述

再判断是否是被@Controller或者是@RequestMapping修饰的bean,由于暴露的接口通常都必须被@Controller或者是@RequestMapping修饰,所以可以理解为筛选需要建立映射的bean。
在这里插入图片描述

回到RequestMappingHandlerMapping的实现方法。
在这里插入图片描述
继续步进看实现。
在这里插入图片描述
需要注意的是这里的MethodIntrospector.MetadataLookup是一个@FunctionalInterface,所以这里的匿名实现不包含函数名。
MethodIntrospector.selectMethods返回的map,就是这个类中所有被@RequestMapping修饰的方法。key为Method对象,value是RequestMappingInfo。
RequestMappingInfo可以理解为RequestMapping的实体类,对我们有用的信息是其中包含的pathPattern,也就是@RequestMapping的value。
在这里插入图片描述

在获取了bean中包含的@RequestMapping修饰的方法后,又回到了之前的register,将使用RequestMappingInfo中的信息,为pathLookup赋值,这样一来,当请求进入程序时,就能够找到正确的接口了。

再回顾一下SpringMVC中bean的生命周期。
再拿一张小图做参考。

实例化
填充属性
调用BeanNameAware的setBeanName方法
调用BeanFactoryAware的setBeanFactory方法
调用ApplicationContextAware的setApplicationContext方法
调用BeanPostProcessor的预初始化方法
调用InitializingBean的afterProperitiesSet方法
调用自定义的初始化方法
调用BeanPostProcessor的初始化后方法
bean可以使用了

总结一下,@RequestMapping的初始化过程应当理解为:

@RequestMapping通过pathLookup维护了一个MultiValueMap类型的变量pathLookup,pathLookup是通过RequestMappingHandlerMapping初始化的,而这个初始化的过程发生在Spring容器初始化bean的过程中。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值