Spring Boot出现Request method 'POST' not supported,深入源码原因分析

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42074868/article/details/85001093

工程

  • 项目静态资源目录结构
    目录

testConverter.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
      <form action="testConverter" method="**POST**">
          <input type="text" name="test">
          <input type="submit" value="submit">
      </form>
</body>
</html>
  • 项目说明
    在不使用themleaf的情况下,通过前端以POST方式提交from表单到controller,controller处理后使用InternalResourceViewResolver进行视图解析,转发到静态资源文件夹static下的目标页面

扩展SpringMVC

@Configuration
public class MyWebMvcConfiguration implements WebMvcConfigurer {



    @Override
    public void addFormatters(FormatterRegistry registry) {
        MyConverter myConverter = new MyConverter();
        registry.addConverter(myConverter);//添加自定义Converter
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
    //添加自定义 InternalResourceViewResolv 
     InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
     internalResourceViewResolver.setPrefix("/");// 给contrlloer返回值设置前后缀
 internalResourceViewResolver.setSuffix(".html");
registry.viewResolver(internalResourceViewResolver);
    }

自定义Converter

@Order(1)
public class MyConverter implements Converter<String, Student> {

    @Override
    public Student convert(String s) {
        System.out.println("myconverter");//将前端提交的String转为Student对象
        String[] values = s.split("-");
        String lastName= values[0];
        int age = Integer.parseInt(values[1]);
        int departmentId = Integer.parseInt(values[2]);
        String departmentName =values[3];
        Department department = new Department(departmentId,departmentName,null);
        return  new Student(null, lastName,age,department);
    }
}

Controller

@Controller
public class MyController {
    //@ResponseBody
    @RequestMapping("/testConverter")
    public String testConverter(@RequestParam("test") Student student){
      System.out.println(student);
        return "testConverter2";//转发到static文件夹下的testConverter2.html页面
    }
}

**

问题

**
当前端表单以POST方式提交请求时,返回405错误页面,而以GET方式则可以到目标页面
前端提交数据
控制台打印信息
错误页面

分析

从上面步骤看,控制台成功打印Student信息,证明自定义Converter有效,并成功将前端传过来的String转换为了Student,而且进一步说明Controller在前面代码执行没有问题,那么问题只能发生在return "testConverter2";,那么是什么原因呢?

  • 配置的InternalResourceViewResolver解析视图有误?
    难道InternalResourceViewResolver未起作用?没有将testConverter2解析为/testConverter2.html?但是如果将请求改为GET,则是可以到目标页面的,通过debug的方式发现InternalResourceViewResolver是可以成功解析视图的

  • 上面的异常为不支持POST请求,那么问题出在那呢?
    通过debug,进入DispatchSeverlet,执行doDispatch方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //public class **RequestMappingHandlerAdapter** extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean   
                    //请求将由RequestMappingHandlerAdapter处理    
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {        //**判断请求是否是GET或HEAD**
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                     //RequestMappingHandlerAdapter(ha)处理请求,返回ModelAndView  
                     //debug进去后,会发现问题就是在这一步发生的,具体后面详细介绍
                     //执行父类AbstractHandlerMethodAdapter **handle方法**
                                     //  @Nullable
                                    //public final ModelAndView handle(HttpServletRequest request,                                                                                                                                                         HttpServletResponse //response, Object handler) throws Exception {
                                   //return **this.handleInternal(request, response, (HandlerMethod)handler);**具体见下
                                   
                                                                                  // }
                                               
                    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
 
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

执行handle方法,handle方法调用handleInternal方法(handle方法与hanleInternal方法均为RequestMappingHandlerAdapter父类AbstractHandlerMethodAdapter定义的方法)

 protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);//该方法会检查请求的类型,this为RequestMappingHandlerAdapter类型
        
        //protected final void checkRequest(HttpServletRequest request) throws ServletException {
        //String method = request.getMethod();
       // if (this.supportedMethods != null && !this.**supportedMethods**.contains(method)) {
           // throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
        //} else if (this.requireSession && request.getSession(false) == null) {
           // throw new HttpSessionRequiredException("Pre-existing session required but none found");
      //  }
  //  }
            .....//省略
 }

执行hanleInternal方法中的checkRequest方法
执行结果

执行结果
该方法没有抛出异常,所以handleInternal方法顺序执行,返回ModelAndView

handleInternal返回结果

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

       ModelAndView var15;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);//数据绑定工厂
            //WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);获得数据绑定器
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            .....//省略
            //进行参数处理,进行转换,即使用MyConverter
        invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);  
          ......//省略
}          

回到DispatcherSeverlet的doDispatch方法
在这里插入图片描述
当前浏览器网页状况
网页状况
当前后台打印信息
后台打印信息
注意当前的mappedhandler
在这里插入图片描述
在这里插入图片描述
解析视图
在这里插入图片描述
将解析得到的视图放入候选视图集合中

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
        List<View> candidateViews = new ArrayList();
        if (this.viewResolvers != null) {
            Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
            Iterator var5 = this.viewResolvers.iterator();

            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);//添加候选视图
                }

返回最佳视图对象

@Nullable
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
            if (bestView != null) {
                return bestView;//从candidateViews获取最佳视图对象并返回
            }
        }

执行结果
在这里插入图片描述
返回到DispatcherSeverlet,执行doDispatch

 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行该方法
在这里插入图片描述
执行handleRequest(HttpServletRequest request, HttpServletResponse response)

public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Resource resource = this.getResource(request);//获取资源
        if (resource == null) {
            logger.debug("Resource not found");
            response.sendError(404);
        } else if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            response.setHeader("Allow", this.getAllowHeader());
        } else {
            this.checkRequest(request);
            if ((new ServletWebRequest(request, response)).checkNotModified(resource.lastModified())) {
                logger.trace("Resource not modified");
            } else {
                this.prepareResponse(response);
                MediaType mediaType = this.getMediaType(request, resource);
                if ("HEAD".equals(request.getMethod())) {

找到目标资源
在这里插入图片描述

  • 报错原因(重点)
 protected final void checkRequest(HttpServletRequest request) throws ServletException {
        String method = request.getMethod();
        if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
            throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
        } else if (this.requireSession && request.getSession(false) == null) {
            throw new HttpSessionRequiredException("Pre-existing session required but none found");
        }
    }

从上面代码看,该方法就是判断是否满足条件,然后决定是否抛出异常
执行结果
在这里插入图片描述
该异常构造方法

public HttpRequestMethodNotSupportedException(String method, @Nullable String[] supportedMethods) {
                                            //得到抛出异常的信息
        this(method, supportedMethods, "Request method '" + method + "' not supported");
    }

至此原因以找到,即不支持POST的方式获取静态资源

解决办法

1)使用GET方式,即表单以GET方式提交
2)进行重定向

@Controller
public class MyController {
    //@ResponseBody
    @RequestMapping("/testConverter")
    public String testConverter(@RequestParam("test") Student student){
      System.out.println(student);
        return "redirect:testConverter2.html";//重定向
    }
}

展开阅读全文

没有更多推荐了,返回首页