SpringMVC源码解析---如何根据http请求找到对应的接口方法

1.概述

在平时开发的时候经常会写下面的代码,调用方根据http请求就能够定位到这个login方法。这到底是如何实现的呢?本文围绕这个问题来展开源码的分析。

@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    UserService userService;

    @PostMapping(value = "/login")
    public ActionResult login(String username, String password) {
    
    
       //业务代码...
    }
  //...
}

2.RequestMappingHandlerMapping

SpringMVC的工作流程都比较熟悉吧,如果不太了解的可以先阅读《SpringMVC源码解析–doDispatch》这篇博客。当客户端发起http请求的时候,会被DispatcherServlet拦截,并调用doDispatch方法,在这方法中,有一步是getHandler,也就是根据http请求来寻找我们要的那个方法。

debug调试源码,可以看到此处会遍历handlerMappings,其中有一个Mapping叫做RequestMappingHandlerMapping。从这个Mapping当中,得到了我们想要的目标。可以猜到这个Mapping里面存放了我们平时编写的那些加了Mapping注解的方法,准确的说不能称存放的是方法,姑且先这么叫吧。

那么问题就变成了我们的login这个目标为什么会出现在RequestMappingHandlerMapping当中???

在这里插入图片描述

所以此时我们应该关注RequestMappingHandlerMapping这个对象整个的生命周期,看看它是如何构建出来的,对于Spring的生命周期不太清楚的读者可以先阅读《Spring IOC–Bean的生命周期》这篇博客。

3.RequestMappingHandlerMapping的构建

Spring容器启动后,会开始构建容器,并往容器里面加入很多的Bean,RequestMappingHandlerMapping也是其中之一,RequestMappingHandlerMapping实现了InitializingBean接口。

在Bean的构建过程中,也就是调用AbstractAutowireCapableBeanFactory#doCreateBean方法的时候,会执行initializeBean方法。实现了InitializingBean接口的Bean会执行该接口的实现方法afterPropertiesSet。重点就在这里,我们可以看看RequestMappingHandlerMapping中的afterPropertiesSet做了什么事情。
在这里插入图片描述
设置一些配置信息,然后调用父类的AbstractHandlerMethodMapping#afterPropertiesSet方法,此处调用了initHandlerMethods。看名字是不是感觉和我们要寻找的答案很接近,初始化handlerMethods。
在这里插入图片描述
initHandlerMethods方法遍历了所有的BeanName,也就是所有可能的候选Bean的名字,然后调用processCandidateBean(beanName)来处理候选Bean。
在这里插入图片描述
processCandidateBean当中首先判断Bean的类型,然后调用isHandler方法,判断这个Bean是否是我们想要的Bean
在这里插入图片描述
isHandler方法逻辑很简单,判断这个类是否加了注解Controller或者RequestMapping
在这里插入图片描述
很显然此时的UserController 满足这两个条件,然后会调用detectHandlerMethods方法,这部分的代码对于阅读源码比较少的读者可能会有些困难,使用了函数式编程的写法。其实就是构建了一个对象,作为selectMethods方法的参数,而这个对象就是MetadataLookup< T >接口的实现类,红色框是方法体,属于inspect方法的内容。
在这里插入图片描述
selectMethods方法,此处又有函数式编程的写法,其中inspect方法,其实就是执行上图红色框框的部分。而上图红色框框中 最后返回的是getMappingForMethod方法的执行结果,交给了下面的result。先不管执行的结果是什么。selectMethods方法 执行ReflectionUtils中的doWithMethods方法
在这里插入图片描述
doWithMethods的方法逻辑比较简单,遍历这个类的所有的方法,然后执行doWith方法,此处的doWith方法就是上图红色大框框的部分。
在这里插入图片描述
doWith中执行metadataLookup.inspect方法,也就是会去执行getMappingForMethod。该方法会根据method来构建RequestMappingInfo,该对象记录了匹配这个method的所有需要满足的条件
在这里插入图片描述
方法返回到selectMethods当中,RequestMappingInfo对象赋值给了result,这个RequestMappingInfo会被放入到methodMap当中。

最终selectMethods的执行结果就是得到一个HashMap,key是method,value是RequestMappingInfo。
在这里插入图片描述
返回到detectHandlerMethods,结束了selectMethods,之后就是遍历刚刚得到的map,然后调用registerHandlerMethod
在这里插入图片描述
调用父类的registerHandlerMethod
在这里插入图片描述
调用mappingRegistry的register 在这里插入图片描述
该方法中根据handler和method构建出了handlerMethod对象,

并将mapping作为key,也就是RequestMappingInfo作为key,handlerMethod对象作为value,放入mappingLookup当中。

并将url作为key,RequestMappingInfo作为value放入urlLookup当中。

最后构建一个MappingRegistration对象,作为value,RequestMappingInfo作为key,放入registry当中。
在这里插入图片描述
最终我们要的目标方法login被封装成了MappingRegistration对象。而该对象是RequestMappingHandlerMapping持有的。至此就完成了RequestMappingHandlerMapping对象的构建,我们也就知道了为什么在第二小节中getHandler的时候会在RequestMappingHandlerMapping当中找到结果。

4. getHandler的具体实现

第三节中证明了,我们要的目标方法的确存在RequestMappingHandlerMapping当中。现在回到getHandler方法,看看是如何从这个对象中去定位我们要的目标方法。

执行mapping.getHandler
在这里插入图片描述
调用getHandlerInternal方法,得到handler
在这里插入图片描述
调用父类的getHandlerInternal方法
在这里插入图片描述
重点就在lookupHandlerMethod方法,
在这里插入图片描述
在这里插入图片描述
第一步调用getMappingsByUrl方法,该方法从urlLoopup当中根据urlPath获得对应的value,也就是找到和url相关的RequestMappingInfo

第二步根据RequestMappingInfo中的条件去匹配HandlerMethod,得到的结果如下
在这里插入图片描述
第三步若第二步得到的匹配结果大于1个,那么就要寻找最匹配的一个,最后返回最匹配的handlerMethod

至此就根据http请求找到了目标HandlerMethod

5.遗留问题

留下两个问题给读者,欢迎交流

  1. 为什么Spring容器启动的过程中会去构建RequestMappingHandlerMapping
  2. 匹配多个HandlerMethod的时候,是如何找到最匹配的那一个的。

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/111635146