暖宝宝官方认证铲屎官
springboot源码解析-@RequestMapping是怎么工作的
发布于: 2020-12-01 更新于: 2020-12-03 分类于: java 阅读次数: 

絮叨几句

这是一篇探究源码的文章,想学习一下优秀框架的设计思路,spring的功能太多了,一下子就看完也不可能,也没那个能力,这里就以springMVC中路由映射注解@RequestMapping为切入点,看它是怎么工作的。

学习源码时,方法很重要!!!不建议直接打开源码自己闷头看,这样会很浪费时间,你需要先理清作者的设计思路,再一点点的看明白,会很浪费时间,如果有其它大佬写过你想学习的点,那你完全可以先看下TA的文章,先了解一下大致的结构,再带着自己的疑问,去看源码,这样会事半功倍的,我这篇文章便是如此,全程参观了简书-有点胖的小乐乐写的分析文章,再结合源码调试来学习。

每个人的思路都是不一样的,前面大佬写的挺棒,但是我看的时候是比较吃力的,因为和我的思路差别太大,并且我觉得后面复习时很困难,所以才又写了一篇。前面也说了,学习时要带着问题,不然都没有目的那会很迷茫,不知道到底应该看哪里,所以通篇会以问题的形式递进,从开始一步步去探索源码,为了能说的清楚点,代码贴的比较多,篇幅有点长,如果你只想知道流程,那看完问题4就可撤了😀😀😀,后面只是更一步探索而已 ,并不会帮助你解决实际问题,所以。。。看个人需求吧
调试和源码出自:

  • idea 2019.2
  • springboot v2.0.0
  • spring-web v5.0.4
  • spring-webmvc v5.0.4
    学习时建议使用相同版本,因为项目是在不停维护更新的,版本不同可能处理流程会有不同。

问题1:springboot为什么能开箱即用MVC?

答:因为springboot在APP启动时,自动为我们配置了MVC需要的配置

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration有如下代码

1
2
3
4
5
6
7
8
9
10
11
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}

springboot在这里new了一个DispatcherServlet类的实例,这个类都很熟悉,它位于spring-webmvc package中,是用来处理http请求的,这样APP就有了处理http请求的能力。

对于MVC应用,还要初始化MVC配置,springboot提供了一个org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration类,在这个类中完成了MVC需要的配置,springboot之所以能直接运行MVC APP,原因就在这里。

问题2:如何根据Url找到对应的Controller处理请求?

答:前面说过,DispatcherServlet是处理所有请求的入口,具体方法是doDispatch,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//省略
.....
try {
//省略
.....
try {
//省略
.....
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
//省略
.....
}
catch (Exception ex) {
//省略
}
catch (Throwable err) {
//省略
.....
}
//省略
.....
}
catch (Exception ex) {
//省略
.....
}
catch (Throwable err) {
//省略
.....
}
finally {
//省略
.....
}
}

方法上面的注释并不难理解,都是很简单的英文,简单来说就是该方法是真正将请求映射到对应handler,handler是实现了HandlerMappings接口的实例,程序会去轮询已经注册过的handler,从而找到合适的handler来处理请求。

那接下来看getHandler是如何实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
//省略
.....
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

如上面注释中所述,这里轮询了注册过的HandlerMapping实例,分别调用getHandler方法,尝试找到合适的handler,咱们都知道开发MVC APP时用@ReauestMapping注解标注方法响应什么Url,调试下来,会发现,在IOC容器中已经注册了多个HandlerMapping的实现类,如下图

1

最终会在调用RequestMappingHandlerMapping实例的getHandler方法,找到了处理的handler,调试信息如下图

5

可以看到,RequestMappingHandlerMapping成功根据请示的Url,找到了可以处理的Controller和对应的方法。到了这里,根据Url找Controller的问题解决了,但是又产生了两个新问题
1. RequestMappingHandlerMapping是什么时候注册进来的?看问题3
2. RequestMappingHandlerMapping的getHandler又是怎么处理的?看问题4

问题3:RequestMappingHandlerMapping是怎么注册的呢?

答:在第一个问题中,说过springboot通过WebMvcAutoConfiguration完成了MVC的配置,从而能处理MVC请求。

而在该类中,存在一个静态类public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration,这个静态类有如下方法

1
2
3
4
5
6
7
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}

可以看到这个方法使用注解@Bean标,在APP启动时,IOC容器会扫描并创建实例将其加载到容器中以备使用,由于这里调用的父类DelegatingWebMvcConfiguration的方法,所以再去看这个类继承的类,位于spring-webmvcpackage中,具体位置为org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration,然而DelegatingWebMvcConfiguration这个类并没有该方法,不过该类继承了org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport类,所以咱们继续找,果然WebMvcConfigurationSupport类中有具体实现

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
......
//省略

return mapping;
}

protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}

可以看到new了一个RequestMappingHandlerMapping出来,继而通过@Bean注解提供给了IOC容器,所以到这里咱们知道springboot中如何初始化RequestMappingHandlerMapping了,接下来要看问题2最后三个问题中的第二个了,RequestMappingHandlerMapping是如何获得handler的。

问题4:RequestMappingHandlerMapping的getHandler怎么工作的?

答:RequestMappingHandlerMapping中并没有getHandler的实现,具体的代码是在其继承的抽象类org.springframework.web.servlet.handler.AbstractHandlerMapping中,代码如下

1
2
3
4
5
6
7
8
9
10
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
//省略
.....

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//省略
.....
return executionChain;
}

可知AbstractHandlerMapping调用了getHandlerInternal方法,而getHandlerInternal方法的实现在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping中(这里插一句,抽象类的设计结构很棒有木有,先是一个顶层的handlerMapping映射Controller,再是一个继承了该类的寻找方法的AbstractHandlerMethodMapping,层层往下,不同层次的功能,通过不同抽象类进行分割,都可以让开发者自定义,佩服),代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//省略
.....
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//省略
.....
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

这里看变量名字就比较好理解,先从request中找到路径,根据路径匹配对应的controller和method,调试截图如下

3

可以看到,根据路径“/hello/test”定位到了HelloController中的test方法,找到了处理请求的方法,后面的流程就是调用该方法,处理请求,进而返回响应结果,比如在测试代码中,/hello/test返回的是一个jsp模板,执行完test方法后会返回ResourceHttpRequestHandler对象,由浏览器渲染html页面。
到这里,其实已经知道springMVC通过url映射Controller处理请求的流程了,如果只想知道处理流程的童鞋,可以ctrl+w了。😂 😂 😂

不过如果有足够的好奇心,可能还会有下面的问题

问题5:在哪里找到的对应的Controller和method的呢?

答:在APP启动时,springboot已经遍历扫描了所有注册的Controller,进而扫描到所有定义的路径,存储到集合中,所以当请求到达时,可以直接从已经存在的集合中找到对应的Controller和method
还是看源码,咱们看AbstractHandlerMethodMapping中定义的getHandlerInternal方法,这个方法中,又调用了lookupHandlerMethod寻找处理请示的method,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {

//省略
.....
return bestMatch.handlerMethod;
}
else {
//省略
.....
}
}

可以看到,根据请求路径“/hello/test”去mappingRegistry中匹配对应的handler,调试发现mappingRegistry信息如下

6

是不是清楚明了呢,咱们通过@RequestMapping注解标注的路径都在这里面存过了,那mappingRegistry是什么呢

1
private final MappingRegistry mappingRegistry = new MappingRegistry();

mappingRegistry是AbstractHandlerMethodMapping中MappingRegistry的实例,因为AbstractHandlerMethodMapping实现了InitializingBean接口,实现了该接口的类在实例化时,spring会自动调用afterPropertiesSet方法,具体的原理,请参考博客园网友的作品(传送门

AbstractHandlerMethodMapping实现的afterPropertiesSet代码如下

1
2
3
public void afterPropertiesSet() {
initHandlerMethods();
}

再看initHandlerMethods的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
protected void initHandlerMethods() {
//省略
.....
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));

for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
//省略
.....
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

/**
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());

if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
//省略
.....
}
});
//省略
.....
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}

initHandlerMethods方法中,

1
2
3
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));

获取IOC容器中注册的对象,咱们程序中的类也在其中,看下图

8

之后会轮询这些对象,判断其是否为处理请求的handler类型

1
2
3
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}

如果是,则在detectHandlerMethods中用反射的方式获取类信息,如方法和方法注解这些,根据咱们标注的@RequestMapping,将Url与method,并将其注册到mappingRegistry对应的集合中,所以就如在问题2所述中,当http请求到来时,springMVC就可以根据Url来找到对应的Controller和method,处理和响应请求。
好了,到这里我觉得可以结束了,篇幅挺长的,如果你看到了这里,那请接受我的敬意,真的很有毅力,希望能一起进步,用知识武装自己,让自己变得更好更强大💪
参考文章简书-有点胖的小乐乐

--- 本文结束 The End ---