最近在写JavaEE系列的文章,在写SpringMVC的REST风格URL的时候出现了一些问题,下面是部分代码。
index.jsp页面代码:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="TestRest PUT">
</form>
<br>
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="TestRest DELETE">
</form>
<br>
<form action="springmvc/testRest" method="post">
<input type="submit" value="TestRest POST">
</form>
<br>
<form action="springmvc/testRest/1" method="get">
<input type="submit" value="TestRest GET">
</form>
<br>
</body>
</html>
控制器代码:
@RequestMapping("/springmvc")
@Controller
public class RequestMappingTest {
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
public String testRestPut(@PathVariable("id") Integer id) {
System.out.println("testRest PUT:" + id);
return "success";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
public String testRestDelete(@PathVariable("id") Integer id) {
System.out.println("testRest DELETE:" + id);
return "success";
}
@RequestMapping(value = "/testRest",method = RequestMethod.POST)
public String testRestPost() {
System.out.println("testRest POST");
return "success";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.GET)
public String testRestGet(@PathVariable("id") Integer id) {
System.out.println("testRest GET:" + id);
return "success";
}
}
项目运行起来:
点GET、POST都没有问题,但你点DELETE和PUT的时候程序就报错了,报错信息如下:
报错信息提示:jsp只允许GET POST或HEAD。
这个报错其实很早之前我就遇到了,当时查了一下,总共有四种方式解决:
- tomcat换到7.0以及以下版本
- 在方法上标注@ResponseBody
- 请求先转给一个Controller,再返回jsp页面
- 在你的success页面头部设置isErrorPage属性为true
当时也确实就解决问题了,也没有深究到底是为什么,这几天大概地查了一下,网上写这个错误的人很多,但也只是给出了解决方案,并没有说到底为什么这样解决。
tomcat换到7.0以及以下版本
查阅了很多资料后,我得出一些结论,报错的信息其实很明显了,说的是jsp只允许GET、POST或HEAD,而我们使用了REST风格中的DELETE和PUT,显然就会报错了。
那么为什么把tomcat版本切换到7.0或者7.0以下的版本就不会出现这样的问题呢?
Tomcat按照JCP规范(JSP2.3版本)的规定,从Tomcat8.x版本开始,不再支持以HTTP PUT方式访问JSP页面,仅支持GET、POST和HEAD方式。
而你在控制器方法中编写的返回值是一个字符串,SpringMVC会认为这是一个jsp页面,所以报错了。
这就完美地解释了第一种解决办法为什么能够起作用,但是切换tomcat版本显然并不好。
在方法上标注@ResponseBody
刚刚说到SpringMVC会将控制器方法的返回值认为是一个jsp页面导致出错,那么你就可以在处理方法上标注@ResponseBody注解,再运行项目试一试:
运行成功,但是返回值显示到了页面上。
这就要来了解一下@ResponseBody的作用了:
@ResponseBody注解的作用是将控制器方法的返回值通过适当的转换器转换为指定的格式之后,写入到Response对象的body区,通常用来返回JSON数据或者是XML数据。
注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过Response对象输出指定格式的数据。
看到这,你是不是就明白了,你加上这个注解,它就不走视图处理器,当然也就不会跳转到jsp页面了,不跳转到jsp页面,当然就不报错了。
不过这个注解通常是用来返回数据的,如果你确实是要返回数据,这样写当然没有问题,这也是比较规范的一种写法。
请求先转给一个Controller,再返回jsp页面
而如果你仅仅就是想跳转一个jsp页面,就可以用第三种解决方法。
既然不能直接跳转到jsp页面,你就可以将请求先转给一个控制方法,再通过该控制方法跳转到jsp页面。
修改一下控制类的代码:
@RequestMapping("/springmvc")
@Controller
public class RequestMappingTest {
@RequestMapping("/toSuccess")
public String toSuccess() {
System.out.println("toSuccess");
return "success";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
public String testRestPut(@PathVariable("id") Integer id) {
System.out.println("testRest PUT:" + id);
return "redirect:/springmvc/toSuccess";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
public String testRestDelete(@PathVariable("id") Integer id) {
System.out.println("testRest DELETE:" + id);
return "redirect:/springmvc/toSuccess";
}
@RequestMapping(value = "/testRest",method = RequestMethod.POST)
public String testRestPost() {
System.out.println("testRest POST");
return "success";
}
}
......
通过这样的方式,我们的DELETE和PUT请求就不会直接地去跳转jsp页面,而是先交给了toSuccess控制方法,并由该方法跳转到jsp页面。
在你的success页面头部设置isErrorPage属性为true
至于这种解决方法为什么能够成功,相信你们应该能自己知道了吧?
就是因为DELETE和PUT请求直接跳转jsp页面会出错,当你在待跳转的jsp页面中设置isErrorPage属性为true后,在跳转jsp页面时出错,而设置了isErrorPage属性的页面即为错误页面,它就这样显示出来了。
总结
综上所述,这四种解决方法其实都是在解决同一个问题,就是jsp不支持DELETE和PUT,我们要想办法在这两种请求的方式下不直接去访问jsp就行了。
但这些方法总归是有些违背自己的主观意愿,所以只有当你需要使用DELETE和PUT请求时才去使用它们,比如通过它们返回一些数据,否则就不要去用它们了,这是多此一举。
老师常常教导我们,要知其然,还要知其所以然。