Spring Boot出现Request method 'POST' not supported,深入源码原因分析
工程
- 项目静态资源目录结构
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);**具体见下 23493 // } 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
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";//重定向 } }
- 在spring中使用ajax出现 Request method 'POST' not supported
- Spring MVC出现POST 400 Bad Request &405 Request method 'GET' not supported
- Spring源码分析之BeanPostProcessor接口和BeanFactoryPostProcessor接口方法不执行原因分析
- curl发送post请求出现:Request method 'post' not supported
- Spring Boot 源码深入分析
- springMVC:org.springframework.web.servlet.PageNotFound.handleHttpRequestMethodNotSupported Request method 'POST' not supported
- Spring源码分析之BeanPostProcessor接口和BeanFactoryPostProcessor接口方法不执行原因分析
- Spring Boot+Shiro+Redis(redisson)整合时,采用内嵌tomcat启动错误原因分析
- spring boot 源码解析54-AbstractHandlerMethodMapping
- Spring boot: Request method 'DELETE' not supported, Request method 'PUT' not supported, Request method 'POST' not supported
- Spring Boot - security 实战与源码分析
- (转)spring boot实战(第三篇)事件监听源码分析
- spring boot实战(第九篇)Application创建源码分析
- spring boot实战(第十篇)Spring boot Bean加载源码分析
- springboot源码分析之环境属性构造过程1
- [Spring Boot] 1. Spring Boot启动过程源码分析
- org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'PUT' not supported
- spring boot实战(第三篇)事件监听源码分析
- Spring Boot+Shiro+Redis(redisson)整合时,采用内嵌tomcat启动错误原因分析
- 深入JVM分析spring-boot应用hibernate-validator