最近在寫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請求時纔去使用它們,比如通過它們返回一些數據,否則就不要去用它們了,這是多此一舉。
老師常常教導我們,要知其然,還要知其所以然。