带你一步一步深入源码!-SpringMVC的请求映射原理

13 篇文章 0 订阅
9 篇文章 0 订阅

请求映射原理

问题情景

你是否有这样的疑问:我编写的Controller中,我写的请求Mapping,SpringBoot怎么知道我要的是这一个,它是如何精准的执行我需要的方法的?还有,欢迎页的显示,我并没有写的Mapping,但是SpringBoot能找到,为什么?那么接下来,本文将共同带你一起一步一步深入源码,请准备好你的IDEA,一步一步跟着本文来慢慢理解,相信你会有所收获,我们带着问题来看看!

问题剖析

我们知道,在springboot中,我们的web请求都是交给springmvc中来做的,而springmvc中的web请求全部都要经过DispatcherServlet,所以,想研究这一块的问题,我们就必须往DispatcherServlet中看一看

IDEA中查找源码DispatcherServlet(shift+shift可以打开查找)

然后找到后,快捷键ctrl+h,可以打开继承树,继承关系一目了然

请添加图片描述

点进HttpServlet查看,我们知道,在原生的servlet中,有get请求时会执行doGet方法,再进入子类,HttpServletBean中查找是否有这样的方法(Ctrl+f12可以查看该类中的代码结构),并没有看见doGet,那可能就是它的子类,继续往下走,看见FrameworkServlet,一查,发现有doGet,再看,调用的时本类的doService

请添加图片描述

只可惜是个抽象方法,它还有子类,一定是交给了子类去实现,再往下走到DispatcherServlet中,查找doServce

,其它的乱七八糟的代码我们不看,看样子就是些赋值啥的语句,我们找到与原生请求与响应相关的代码,找到如下:

try {
   doDispatch(request, response);
}
	....
}

发现就是调用的一个方法,我们再找这个方法,ctrl点进这个方法,发现是一个本类中的内部方法,接着来看看内部方法里面有什么,方法如下:

@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   ....

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);//这是一个文件上传请求

         // Determine handler for the current request.(注意!中文翻译:确定当前请求的处理程序)
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
          .....
      }
   }
}

那就让我们DeBug一下看看,那句注释后面的一句话是如何执行的吧!把断点打在用getHandler方法的语句上 (handler大家可以理解为就是我们写的一些Controller)

正常运行后访问:localhost:8080/user?,再debug运行,这样可以让我们看见主页,单步运行后跳到本类的一个方法,如下:(已将英文注释翻译)

请添加图片描述

点开第一个RequestMappingHandlerMapping,找到mappingRegistry(注册映射),发现里面有两个,其中有一个个是我们自己写的**/use**r访问路径,还有一个是内置的error页面,回想好像springboot中web场景会有这样的404页面,是不是有点眉目了?别急这才刚刚开始。

请添加图片描述

那再让我们分析一下源码

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

for:each循环,就是总共有四个,找到能够处理这样请求的Mapping,交给它去执行请求。

那么关键点就落在了下面这句代码上

 HandlerExecutionChain handler = mapping.getHandler(request);

如上源码,这句话就在于匹配对应的程序处理请求,我们debug步入看看

来到AbstractHandlerMapping中的getHandler方法。

请添加图片描述

第一句话就很重要,将传进来的request去找到内部处理的程序,我们再步入看看

到了RequestMappingInfoHandlerMapping中的getHandlerInternal方法

请添加图片描述

再步入,来到AbstractHandlerMethodMapping中的getHandlerInternal方法

请添加图片描述

步过 运行,来到lookupHandlerMethod方法,我们看见,lookupPath变量被解析为了/user,也就是说,当前servlet映射中的映射查找的路径是/user,然后再进入到lookupHandlerMethod方法,将原生的request和要查找的/user传入进行匹配,最终匹配出我们要执行的处理程序,但是这里可能处理程序有两个,因此是一个list集合,而springmvc是不允许我有两个请求的!所以可以看见,在源码中***matches.size() > 1*就是一个体现,如果大于一个,那就控制台输出一个错误,因此这就完成了一个匹配!执行了我们在RequstMapping(“/user”)**中的处理请求。

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
   if (directPathMatches != null) {
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
   }
   if (!matches.isEmpty()) {
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
         matches.sort(comparator);
         bestMatch = matches.get(0);
         if (logger.isTraceEnabled()) {
            logger.trace(matches.size() + " matching mappings: " + matches);
         }
          ...

总结

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。

自定义 HandlerMapping:

有的时候比如说同一组api有不同的版本,比如v1,v2我们可以在controller中写两组mapping(比如v1/user,v2/user),但同时我们也可以放在两个包下,都是/user,这个时候我们就可以自定义handlermapping,把v1/user映射到一个包下的/user,把v2/user映射到另外一个包下的/user。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值