源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常

异常现象描述

这个异常我相信各位开发人员是绝对出现过,但是也很容易解决,因为前后端分离的项目中,发请求的方式错误就会产生此异常,也就是使用PostMan或者前端发送ajax请求时应该发送get请求方式你偏偏使用了Post请求方式。

前端错误如下:

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第1张图片

后端错误如下:

解决方案

解决方案,就很简单了,主要是想带找bug的您从源码角度分析一下此bug!

  1. 如果是前端或者PostMan的请求方式您选错了就改一下就好
  2. 如果是后端您编写的接口的请求方式写错了改一下就好

从SpringMVC源码分析此问题

我们明白,对于SpringMVC来说是基于tomcat启动,当然Spring boot是内置的tomcat容器。然后tomcat初始化servlet的时候会把Spring整个上下文给刷新,刷新的意义也就是把整个项目中内置bean和开发者写的bean给装配到IoC容器中。并且我们知道对于SpringMVC来说一个非常重要的组件就是HandlerMapping组件,当然整个Spring上下文刷新的时候,也会把HandlerMapping给加载到容器中。在加载HandlerMapping的时候就会检查项目中标有@Controller注解或者@RequestMapping注解的类给解析,把类其中的方法解析成一个HandlerMethod,最后都加入到MappingRegistry中的n个缓存中。

SpringMVC中9大组件之HandlerMapping源码分析_程序员李哈的博客-CSDN博客前言相信能看到此篇帖子的小伙伴们对SpringMVC的大致源码还是有了解,可能是来仔细阅读SpringMVC中每个组件的功能和底层源码实现。对于SpringMVC来说对URL地址中地址处理和地址对应的controller层面的类与接口的映射关系都是HandlerMapping来做处理。所以本篇帖子来解读SpringMVC启动时HandlerMapping组件对上下文的作用。正文很明确的说如今已经不是ssm框架的天下了,基本全是基于Spring boot微服务的天下了,但是呢SpringMVChttps://blog.csdn.net/qq_43799161/article/details/123401797?spm=1001.2014.3001.5502博主b站视频讲解SpringMVC启动源码icon-default.png?t=M276https://www.bilibili.com/video/BV1cL4y1j7ng?spm_id_from=333.999.0.0

说了这么多也就是一个容器启动,那么提它的意义在哪的,笔者的目的是让大家知道大家在Controller层写的接口是如何交给SpringMVC的。那么,当前端或者PostMan发送请求到我们的服务器中。首先是经过tomcat,tomcat将servlet创建好,最后到SpringMVC(dispatcherServlet)中的doDispatch()方法。

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第2张图片

 HttpServlet也是Servlet的子类,也就是最后请求会来到service方法中,在这里对请求方式做了判断,这里也就是对restful风格的请求做判断。

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第3张图片

 判断完请求方式后会来到DispatcherServlet中doDispatch()方法,相信这里大家并不陌生了。那么回顾一下笔者最前面说的在Spring容器刷新的时候,会把当前项目中Controller层的内容给解析放入缓存。所以我们可以推理一把,那就是当请求过来,SpringMVC会根据当前请求的路径在MappingRegistry中做缓存命中。如果命中成功,会再对请求方式做判断,如果不满足就会抛出当前文章标题的异常。下面就是源码论证了。

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第4张图片

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第5张图片 

对上面的步骤做一下暂时的总结(截图中的注释已经说的非常明白):

  1. 在doDispatch()方法中,先是通过HandlerMapping获取到HandlerExecutionChain(请求的详细信息和拦截器链的信息)
  2. 所以使用责任链模式for循环SpringMVC中所有的HandlerMapping,然后找到当前请求处理的HandlerMapping
  3. 找到处理当前请求的HandlerMapping以后再通过HandlerMapping获取到HandlerExecutionChain,也就是获取当前请求的详细信息。
  4. 然后就是根据当前请求去命中缓存和获取到当前的拦截器链封装成HandlerExecutionChain(但是具体命中缓存的步骤还没讲,下面会具体介绍,不过我们就已经知道,我们目前的核心就是在如何命中缓存的操作)

所以看到getHandlerInternal()方法的具体逻辑

源码分析SpringMVC中HttpRequestMethodNotSupportedException: Request method ‘POST‘ not supported异常_第6张图片

 这里通过MappingRegistry的读写锁对并发做安全处理,笔者前面有介绍,详请的请求参数封装在HandlerMethod中,那么命中缓存的逻辑肯定就在lookupHandlerMethod()方法中。

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List matches = new ArrayList<>();
   List 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()) {
      Comparator comparator = new MatchComparator(getMappingComparator(request));
      matches.sort(comparator);
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         if (logger.isTraceEnabled()) {
            logger.trace(matches.size() + " matching mappings: " + matches);
         }
         if (CorsUtils.isPreFlightRequest(request)) {
            return PREFLIGHT_AMBIGUOUS_MATCH;
         }
         Match secondBestMatch = matches.get(1);
         if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            String uri = request.getRequestURI();
            throw new IllegalStateException(
                  "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
         }
      }
      request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
   }
   else {
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
   }
}

这边就是通过当前请求的路径地址,对MappingRegistry中的urlLookup缓存做命中,如果命中了就再对当前的请求方式做判断,如果这里没命中就代表当前请求方式是错误的,但是这次没命中没关系,因为他会获取到MappingRegistry中mappingLookup(这个缓存是最全的,MappingRegistry中为什么存在这么多缓存就是为了空间换时间,而且缓存多,可能先从轻量级缓存入手,如果没命中就使用重量级缓存)缓存再来对当前请求方式再做一遍判断,如果还是对应不上就会走到最下面的else中抛出本文章的标题的异常。

总结

问题很容易解决,但是想从源码层面了解这个问题,需要对SpringMVC的一个执行流程和启动流程有一定的了解,不了解的读者可以从上面的链接跳转学习,都是笔者写的文章或者笔者B站的视频。

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和源码解读~!

你可能感兴趣的:(Spring,MVC系列,源码解读,源码分析,java,后端,spring,SpringMVC)