您的位置:首页 > 编程语言 > Java开发

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

2018-12-15 00:18 1911 查看

工程

  • 项目静态资源目录结构

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";//重定向
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐