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去注册

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容