SpringMVC源码 (1)- HandlerMapping

1.SpringMVC的基本请求流程

SpringMVC如果大家都用过的话,可能会有熟悉这个叫做DispatcherServlet的类,那这个类是干嘛的呢?
其实就是所有请求的入口。所以先看里面的每个请求必定会经过的方法。然后引出我们这次要将的HandlerMapping以及HandlerMapping的作用

下面是doDispatch的大致的调用流程,我这里只留下了关键的点

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
    
       ModelAndView mv = null;
        Exception dispatchException = null;

        //  1.获取一个HandlerExecutionChain
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        //  2.获取一个HandlerAdapter来处理这个请求
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
       
       // 3.处理请求的前置调用
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // 4.真正调用处理方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
       
       // 5.后置请求的处理
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        
        // 6.处理返回的结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, 
dispatchException);

    }

看上面的代码就能知道大致的流程

1. 获取一个HandlerExecutionChain
2. 获取一个HandlerAdapter来处理这个请求
3. 处理请求的前置调用
4. 真正调用处理方法
5. 后置请求的处理
6. 处理返回的结果

2.HandlerExecutionChain的作用

如果去看源码,就会发现绕来绕去就是为了获取这个HandlerExecutionChain,那HandlerExecutionChain是个啥,我为啥要获取到它,他能干嘛,为什么还没有讲到HandlerMapping呢?别急先讲一下HandlerExecutionChain

首先先看下这个类的关键成员变量,以及关键的函数

public class HandlerExecutionChain {
   
   // 这个对象一般情况下就是一个HandleMethod对象
   private final Object handler;
   
   // 拦截器,可以对一个请求进行前置,后置拦截
   private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
   
   // 拦截器的下标
   private int interceptorIndex = -1;
   
   // 前置拦截器,用于拦截请求
   boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for (int i = 0; i < this.interceptorList.size(); i++) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
        return true;
    }
  // 剩下的还有后置拦截器等等
}

所以上面的代码我就就能看到其实HandlerExecutionChain,就是HandleMethod + List<HandlerInterceptor>的结合体,用来对HandleMethod处理的时候进行拦截的,这里又引出了一个HandleMethod, 这个HandleMethod又是啥呢,这里直接说明,他其实就是这次请求对应的Method(处理方法)

所以我们就知道了只要是找到了这个HandlerExecutionChain,其实就相当于找到了这个请求是对应哪个Controller的哪个方法,以及哪些拦截器,有了这些我们后续是不是就可以根据这些信息就能进一步解析参数,调用Method的了呢。

3.HandlerExecutionChain和HandlerMapping的联系

根据上面的流程,我们能看到第一步就是获取HandlerExecutionChain,那看一下获取方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings != null) {
    // 遍历循环HandlerMapping来获取HandlerExecutionChain
    for (HandlerMapping mapping : this.handlerMappings) {
      HandlerExecutionChain handler = mapping.getHandler(request);
      if (handler != null) {
        return handler;
      }
    }
  }
  return null;
}

可以看到这里HandlerMapping就是用于获取HandlerExecutionChain,那我们看一下这个接口有啥

public interface HandlerMapping {
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

其实这个接口核心方法就这一个,那我就需要看下哪些地方实现了这个接口


image.png

引用网上的一张图

4.HandlerExecutionChain对象的生成

左半部分黄色框话用的比较少,右半部分红色的是我们常用的根据@Controller@RequestMapping来处理请求的方式,那就对这个分析

可以看到继承关系:
HandlerMapping
-> AbstractHandlerMapping
---> AbstractHandlerMethodMapping
------> RequestMappingInfoHandlerMapping
---------> RequsetMappingHandlerMapping

既然是要看怎么获取的HandlerExecutionChain,那根据调用关系,先到AbstractHandlerMappinggetHandler,这个也是HandlerMapping需要实现的接口。

@Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
         // 留给子类去实现
        Object handler = getHandlerInternal(request);
           // 组合成HandlerExecutionChain 
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        return executionChain;
    }

这个getHandlerInternal是个抽象方法,那就是会由子类去实现,那继续看子类,会发现最终是由AbstractHandlerMethodMapping去实现的,后面的RequestMappingInfoHandlerMapping虽然也实现了这个函数,但是最终也是调用AbstractHandlerMethodMappinggetHandlerInternal

那我们看下面的AbstractHandlerMethodMappinggetHandlerInternal的实现

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 1.找请求的路径,比如请求的路径/test/getInfo,那这里就是这个/test/getInfo
        String lookupPath = initLookupPath(request);
        // 2.加锁
        this.mappingRegistry.acquireReadLock();
        try {
            // 3.根据请求的路径找HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

1.可以看到主要是根据请求的路径去找对应的HandlerMethod(处理方法),这里就和前面HandlerExecutionChain的作用里面的HandlerMethod是对应起来了的,这个HandlerMethod会被拿来存到HandlerExecutionChain中,然后被调用。

2.那我们继续看是如何根据路径找对应的HandlerMethod,看lookupHandlerMethod方法。

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        // 1.获取匹配上的路径的信息(RequestMappingInfo)
        List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
        if (directPathMatches != null) {
            // 2.将匹配上的路径信息和对应HandleMethod添加到matches里面
            addMatchingMappings(directPathMatches, matches, request);
        }
    
        if (!matches.isEmpty()) {
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                // 大于两个可以匹配的路径的时候,进行排序
            }
            

            // 3.返回HandleMethod
            return bestMatch.getHandlerMethod();
        }
        else {
            return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
        }
    }

这里将关键步骤单独取出来

1.获取匹配上的路径信息
2.将匹配上的路径信息和对应HandleMethod添加到matches里面
3.返回HandleMethod

2.1 this.mappingRegistry.getMappingsByDirectPath(lookupPath);

那这里一个最为关键的就是mappingRegistry这个对象,这个里面是干啥的呢,我先解释下基本的,后面我们再来分析。
这类看名字就知道是映射的注册,是什么映射的注册呢?其实是这个类会保存我们定义的@Controller里面的处理方法的信息,比如我写了下面的代码:

@RestController
@RequestMapping(value = "/test")
public class TestController {

    @ResponseBody
    @PostMapping(value = "/map")
    public ResponseDto map(@RequestBody RequestVo vo) {
        ResponseDto dto = new ResponseDto();
        dto.setName("jiang");
        return dto;
    }
}

那这个mapping就会将这个Controller的信息提取出来,包括他匹配的路径/test/map,以及这个路径对应的TestController.map方法。
这些信息提取出来后,就注册到这个mappingRegistery,我们可以简单的把它当作一个Map,Key是/test/map, Value是TestController.map方法。 后面我们会分析这个是如何注册的,这里先跳过。
这里就是将/test/map对应的注册的信息取出来,这里是最简单的根据路径匹配,只要匹配上就会返回

2.2 addMatchingMappings(directPathMatches, matches, request)

这个方法也不算复杂,就是对上面2.1的再次进行匹配筛选,其中包含:

1.匹配POST和GET方法是否一致
2.匹配要求的请求头是否一致
3...

根据上面的条件筛选完后,就会组装得到一个新的请求路径、请求头、请求方式等都完成符合这次请求的RequestMappingInfo信息

    public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
          // 判断是否请求的POST或者GET这种方式匹配
        RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
        if (methods == null) {
            return null;
        }
        // ...省略别的判断

        // 返回一个新的请求路径、请求头、请求方式等都完成符合这次请求的RequestMappingInfo信息
        return new RequestMappingInfo(this.name, pathPatterns, patterns,
                methods, params, headers, consumes, produces, custom, this.options);
    }

,就将这个加到matches

matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));

2.3 return bestMatch.getHandlerMethod();

这里就是直接返回HandlerMethod了,然后将也会调用HandlerMethod.createWithResolvedBean将这个Controller通过this.beanFactory.getBean(beanName)获取到,再重新组装个HandlerMethod返回给AbstractHandlerMapping.getHandler

生成HandlerExecutionChain总结:

那么这里就已经生成了HandlerExecutionChain,我们总结一下流程:
1.调用AbstractHandlerMapping的getHandler
2.调用到子类的AbstractHandlerMethodMapping的getHandlerInternal
3.调用AbstractHandlerMethodMapping.lookupHandlerMethod,通过mappingRegistry获取到这次请求路径对应的Controller的方法信息(RequestMappingInfo)
4.校验上面第3步获取的RequestMappingInfo信息的请求方式(POST,GET),请求头是否符合要求,选出最合适的返回
5.取出第4步的RequestMappingInfo,将里面HandlerMethod(可以简单理解为就是Controller的方法)和这次请求的Request一起组合成HandlerExecutionChain

5.请求注册表(mappingRegistry)是如何注册的

前面我们提到了这个mappingRegistry是很关键的,把@Controller这些的类的方法注册到里面,那是如何注册的呢,我们下面分析一下

1.AbstractHandlerMethodMapping.InitializingBean

mappingRegistry的加载注册是在AbstractHandlerMethodMapping这个类的实现的,那我们先看这个类

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

这个类实现了InitializingBean ,所以到里面看一下主要是这个方法initHandlerMethods

    protected void initHandlerMethods() {
  // 获取所有的bean
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 对这些bean处理
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

这里就是把所有Spring的Bean都取出来,然后挨个去调用processCandidateBean

2.processCandidateBean

    protected void processCandidateBean(String beanName) {
        // 判断是否是匹配当去处理器的,也就是`AbstractHandlerMethodMapping`的子类的`isHandler`是true
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

这里有一个重点,isHandler,这个是个抽象函数,在这里只有RequestMappingHandlerMapping实现了。这个函数是用于判断这个Bean是否是能被RequestMappingHandlerMapping处理的。
我们看一下它的实现:

  protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

这里就很明显了吧,那些被打算了@Controller,或者RequestMapping注解的类就会通过,进入detectHandlerMethods,然后被扫描,分析。注册到mappingRegistery

3.检测那些@Controller或者@RequestMapping的类对应的请求

我们继续进入到detectHandlerMethods

    protected void detectHandlerMethods(Object handler) {
        // 1.获取这个类的信息,这里就是一个Controller的Class
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            // 2.获取用户自定义的Class,有些是cglib的,
            Class<?> userType = ClassUtils.getUserClass(handlerType);

            // 3.获取有RequestMapping注解的方法
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            
            // 4.循环方法,注入到mappingRegistry
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

梳理一下主要流程:

1.获取这个类的信息,这里就是一个Controller的Class
2.获取用户自定义的Class,有些是cglib的
3.获取有RequestMapping注解的方法
4.循环第三步的方法,注入到mappingRegistry

其中最关键的就是第三步,他是怎么去获取由RequstMapping的注解的方法的呢

4.获取RequstMapping的注解的方法

先把代码再复制一遍看下:

        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });

这里MethodIntrospector.selectMethods这个方法是由Spring-Core提供的,并不是SpringMVC独有的,那我们这先说下这个是干嘛的.
其实就是他会遍历传入的类的所有方法,然后每次遍历一个方法都会调用传入的MetadataLookup

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) 

具体这个函数大家可以跟进去看,其实他就是把这个targetType的类、父类以及实现的接口,都找出来,然后再一个一个的遍历他们的方法。

下面是伪代码:

  // 1.找出所有的类,包括类和接口
  List<Class<?>> clzs = findAllClassAndInterface();
  // 2.遍历类和接口
  for (Class clz : clzs) {
   // 3.找出所有的方法
    List<Method> methods = clz.getAllMethods();
   // 4.遍历方法
   for (Method method : methods) {
      // 5.调用回调函数
      metadataLookup.inspect(method);
   }
}
···

那这里我们就需要看一下我们传入的这个MetadataLookup是啥。
```java
(MethodIntrospector.MetadataLookup<T>) method -> {
  try {
    return getMappingForMethod(method, userType);
  }
  catch (Throwable ex) {
    throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);
  }
}

这里调用了getMappingForMethod,这个函数最终由RequestMappingHandlerMapping实现了。

    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        // 1.创建方法请求映射信息
        RequestMappingInfo info = createRequestMappingInfo(method);
        
        // 2.如果创建成功,走下面
        if (info != null) {

            // 2.创建对象(Controller)请求映射信息
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                // 3.两个结合成一个完整的新的信息
                info = typeInfo.combine(info);
            }
            String prefix = getPathPrefix(handlerType);
            if (prefix != null) {
                info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
            }
        }
        // 4.返回请求映射信息
        return info;
    }

这里看一下createRequestMappingInfo

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

这里很清晰了把,就是获取方法上面的@RequestMapping注解,没有的话就不创建了,只有打上了这个注解的才能被创建注册。

再进入到createRequestMappingInfo,这里是真正创建的地方:

    protected RequestMappingInfo createRequestMappingInfo(
            RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(this.config).build();
    }

这里用了RequestMappingInfo的builder,看一下就知道,其实就是把注解上面的路径、参数、请求头这些设置进去然后返回。

这里就创建好了MethodRequestMappingInfo,接下面就将类的RequestMappingInfo也创建好了。

RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }

最终将方法的和类的RequestMappingInfo组合一下,返回给methods。注入到mappingRegistry

这里再说一下为什么要组合一下。
因为注入到mappingRegistry,是要全的信息。比如再@Controller的@RequestMapping的是路径/test,然后里面的方法@RquestMapping注解是路径/map,那他们组合一下就是全的请求路径/test/map。 不仅这些路径,还有些别的属性也是需要整合,然后封装到一个RequestMappingInfo返回给mappingRegistry

这样我们就知道了一个@Controller是如何注册到mappingRegistry的了。

总结

1.AbstractHandlerMethodMapping.InitializingBean
2.processCandidateBean里面判断是否有@Controller或者@RequestMapping
3.将第二步检测通过的类的方法进行遍历,包括它的父类的,接口的方法
4.遍历方法的时候获取到@RequestMapping上面的信息,生成RequstMappingInfo
5.将类和方法的RequstMappingInfo合并成新的,返回给mappingRegistry去注册

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容