此次接着上次的博客繼續將springMVC控制器的東西說完。本篇主要說說控制器處理帶屬性參數的url請求的三種方式:參數風格、rest風格、傳統的HttpServlet風格。css
參數風格html
其實,上篇博客已經在示例當中將參數風格的實現方式給出了,不過沒有詳細說明。java
所謂參數風格,就是講url的請求參數按照url請求參數的格式予以呈現,咳咳,彷佛有點廢話,不過這種方式應該是最通常的方式,也是過去一直用的。web
好比,請求 http://localhost:8080/mvc/courses/view?courseId=123 spring
控制器寫法:(完整寫法借鑑上一篇博客,或者在本文最後一併給出新的)數據庫
//提供完成一個業務的方法:根據課程ID查詢課程內容。 //本方法將處理 /courses/view?courseId=123 形式的URL @RequestMapping(value="/view", method= RequestMethod.GET) public String viewCourse(@RequestParam("courseId") Integer courseId, Model model) { //日誌輸出,查看請求的courseId是否是咱們的courseId log.info("In viewCourse, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.addAttribute(course); return "course_overview"; }
這裏,參數風格的處理方式,須要接收url中的參數,需要藉助@RequestPara註解。@RequestPara註解在方法的某個參數變量上,而後註解@RequestPara的註解value設置爲url中參數值,這樣@RequestPara就創建起了url的參數與方法參數之間的映射,也就完成了參數的傳遞。好比,這裏的請求 http://localhost:8080/mvc/courses/view?courseId=123中的請求參數courseId與方法參數上註解@RequestParam("courseId")中的"courseId"是同一個,或者說必須一致。而方法public String viewCourse的方法參數Integer courseId也未必必定要與其同名。apache
另外,方法的兩個參數Integer courseId和Model model分別負責請求參數的接收 和 結果屬性的返回。Model model能夠接收咱們須要返回或者說須要在view的JSP頁面顯示中要用到的某個屬性對象。例如,這裏的model.addAttribute(course);裏的course必須與下面\WEB-INF\jsps\course_overview.jsp中的代碼段中course同名,這是這種model.addAttribute默認的。固然將結果返回也有三種方式,即model、Map、ModelAndView。這個之前的博客中說過,不過springMVC會在內部自動將它們都轉換成ModelAndView。api
Map返回方式我在下面一個rest風格再做說明。服務器
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>HappyBKs喜歡原先的osc博客頁面</title> <link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" /> </head> <body> <div id="main"> <div class="newcontainer" id="course_intro"> <div class="course-title">${course.title}</div> <div class="course_info"> <div class="course-embed l"> <div id="js-course-img" class="img-wrap"> <img width="600" height="340" alt="" src="<%=request.getContextPath()%>/${course.imgPath}" class="course_video" /> </div> <div id="js-video-wrap" class="video" style="display: none"> <div class="video_box" id="js-video"></div> </div> </div> <div class="course_state"> <ul> <li><span>學習人數</span> <em>${course.learningNum }</em></li> <li class="course_hour"><span>課程時長</span> <em class="ft-adjust"><span>${course.duration }</span>秒</em></li> <li><span>課程難度</span> <em>${course.levelDesc }</em></li> </ul> </div> </div> <div class="course_list"> <div class="outline"> <h3 class="chapter_introduces">課程介紹</h3> <div class="course_shortdecription">${course.descr}</div> <h3 class="chapter_catalog">課程提綱</h3> <ul id="couList"> <c:forEach items="${course.chapterList}" var="chapter"> <li class="clearfix open"><a href="#"> <div class="openicon"></div> <div class="outline_list l"> <!-- <em class="outline_zt"></em> --> <h5 class="outline_name">${chapter.title }</h5> <p class="outline_descr">${chapter.descr }</p> </div> </a></li> </c:forEach> </ul> </div> </div> </div> </div> </body> </html>
控制檯輸出:cookie
Rest風格:
最新的處理請求的方式應該算是Rest風格了。
什麼是Rest風格?
REST ( REpresentational State Transfer ),State Transfer 爲 "狀態傳輸" 或 "狀態轉移 ",Representational 中文有人翻譯爲"表徵"、"具象",合起來就是 "表徵狀態傳輸" 或 "具象狀態傳輸" 或 "表述性狀態轉移",不過,通常文章或技術文件都比較不會使用翻譯後的中文來撰寫,而是直接引用 REST 或 RESTful 來表明,由於 REST 一整個觀念,想要只用六個中文字來完整表達真有難度。
REST的主要原則有:
用URL表示資源。資源就像商業實體同樣,是咱們但願做爲API實體呈現的一部分。一般是一個名詞,每一個資源都用一個獨一無二的URL來表示。
HTTP方法表示操做。REST充分利用了HTTP的方法,特別是GET、POST、PUT和DELETE。注意XMLHttpRequest對象實現了所有的方法,具體能夠參看W3C HTTP 1.1 Specification。
也就是說,客戶端的任何請求都包含一個URL和一個HTTP方法。回到上面的例子中,比賽顯然是一個實體,那麼對於一個特定比賽的請求就表示爲:
http://example.com/matches/995
這種方式是清晰明瞭的,也許和精確命名的方式有所區別,可是隻要遵循這種形式,咱們就能很快的進行GET、DELETE、UPDATE和新建操做。
RESTful的原則:
URL表示資源
HTTP方法表示操做
GET只是用來請求操做,GET操做永遠都不該該修改服務器的狀態。可是這個也要具體狀況進行分析,例如一個頁面中的計數器,每次訪問的時候確實引發了服務器數據的改變,可是在商業上來講,這並非一個很重要的改變,因此仍然能夠接收使用GET的方式來修改數據。
服務應該是無狀態的
在有狀態的會話中,服務器能夠記錄以前的信息。而RESTful風格中是不該該讓服務器記錄狀態的,只有這樣服務器才具有可擴展性。固然,咱們能夠在客戶端使用cookie,並且只能用在客戶端向服務器發送請求的時候。
服務應當是「冪等」的
「冪等」表示能夠發送消息給服務,而後能夠再次絕不費力的發送一樣的消息給服務。例如,發送一個「刪除第995場比賽」的消息,能夠發送一次,也能夠連續發送十次,最後的結果都會保持一致。固然,RESTful的GET請求一般是冪等的,由於基本上不會改變服務器的狀態。注意:POST請求不能被定義爲「冪等」,特別是在建立新資源的時候,一次請求建立一個資源,屢次請求會建立多個資源。
擁抱超連接
服務應當自我說明
例如 http://example.com/match/995 請求了一個具體的比賽,可是 http://example.com/match 並無對任何實體進行請求,所以,應當返回一些介紹信息。
服務約束數據格式。數據必須符合要求的格式
好好好,有個概念便可,仍是用例子說話。剛纔的需求咱們用rest風格實現,請求應該變成這個樣子:http://localhost:8080/mvc/courses/view2/345
固然這裏由於是查詢功能,因此請求的方法類型是Get,固然,默認也是Get。
因而,咱們的控制器方法能夠寫爲:
//本方法將處理 /courses/view2/123 形式的URL @RequestMapping("/view2/{courseId}") public String viewCourse2(@PathVariable("courseId") Integer courseId, Map<String, Object> model) { log.info("In viewCourse2, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.put("course",course); return "course_overview"; }
這裏,咱們在仍然須要爲rest風格中的請求實體與控制器方法中的方法參數創建映射對應關係。這裏,因爲rest風格在url中並無提供實體參數的名稱,只是在url的某個子串內提供,因此須要對該子串的位置進行標明,即用花括號{}在@ResquestMapping的value中進行標註,並取名。如,本例中的@RequestMapping("/view2/{courseId}")。在控制器方法中,對映射的方法參數須要用另外一個註解@PathVariable來註解相應的方法參數,@PathVariable註解value爲@RequestMapping的value中的花括號內的名稱,即@RequestMapping("/view2/{courseId}")的{courseId}與@PathVariable("courseId")中的"courseId"是一致的,而控制器方法參數自己的名稱無所謂。
控制輸出以下:
傳統的HttpServlet風格
這種方式其實仍是處理的是帶參數的url請求的方法,只不過控制器方法是針對底層servlet的處理方式,這種方式是爲了體現通常的servlet編寫方式能夠與springMVC框架相兼容。
請求http://localhost:8080/mvc/courses/view3?courseId=678
控制器的方法參數用的是HttpServletRequest request,固然,取參數什麼的用的仍是傳統的HttpServletRequest的請求實體對象的參數取出方法。實體對象的返回用的也是request.setAttribute("course",course);的處理方式。
//本方法將處理 /courses/view3?courseId=123 形式的URL @RequestMapping("/view3") public String viewCourse3(HttpServletRequest request) { Integer courseId = Integer.valueOf(request.getParameter("courseId")); log.info("In viewCourse3, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); request.setAttribute("course",course); return "course_overview"; }
這裏值得注意的是,返回結果實體的方式不是利用Model、Map或ModelAndView,而是用最老土的方式,request.setAttribute方法來將屬性結果按照鍵值對的形式給出,鍵的名稱是JSP中的實體名稱,值則是控制器方法中處理的實體對象。
這裏還須要注意的是,HttpServletRequest並非標準Java SDK中的類,所以需要爲項目添加依賴jar包:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency>
引入:
import javax.servlet.http.HttpServletRequest;
控制檯輸出:
以上就是控制器處理url請求參數數據的三種方法,下面咱們模擬一個示例,添加一個課程,而後重定向到顯示頁面。
這裏,僅僅是個示例,我不想再添加有關數據庫的操做,因此顯示部分我仍是用
@RequestMapping("/view2/{courseId}")
public String viewCourse2(@PathVariable("courseId") Integer courseId,
Map<String, Object> model)
來代替,因此重定向的數據會顯示爲定死的數據,真實狀況下,應該是Service部分實現了從數據庫中查找到相應課程id的課程實體對象,而後View接受了這個course對象。
首先,添加\WEB-INF\jsps\course_admin\edit.jsp
JSP代碼以下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>HappyBKs課程錄入頁面</title> <link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" /> </head> <body> <div id="main"> <div class="newcontainer" id="course_intro"> <form name="mainForm" action="<%= request.getContextPath()%>/courses/save" method="post"> <div> <span>課程名稱:</span><input type="text" id="title" name="title"> </div> <div> <span>課程時長:</span><input type="text" id="duration" name="duration"> 秒 </div> <div> <span>課程難度:</span> <select id="level" name="level"> <option value="0">初級</option> <option value="1" selected="selected">中級</option> <option value="2">高級</option> </select> </div> <div> <span>課程介紹:</span> <textarea id="descr" name="descr" rows="5" style="width:480px"></textarea> </div> <div> <input type="submit" id="btnPass" value="提交" /> </div> </form> </div> </div> </body> </html>
插一句吧,request.getContextPath()應該是獲得項目的名字(若是項目爲根目錄,則獲得一個"",即空的字條串)。
而後,咱們在控制器中繼續添加一個方法:
@RequestMapping(value="/admin", method = RequestMethod.GET, params = "add") public String createCourse(){ return "course_admin/edit"; }
運行時請求 http://localhost:8080/mvc/courses/admin?add 的顯示效果以下:
固然咱們還得添加一個表單提交以後相應的控制器方法,即表單中action="<%= request.getContextPath()%>/courses/save"所指示的請求url。
控制器中添加方法以下:
@RequestMapping(value="/save", method = RequestMethod.POST) public String doSave(@ModelAttribute Course course){ log.info("Info of Course:"); log.info(ReflectionToStringBuilder.toString(course)); //在此進行業務操做,好比數據庫持久化 course.setCourseId(123); return "redirect:view2/"+course.getCourseId(); }
這裏值得注意的有三點,一個是,標記解釋某個model實體,能夠在控制器方法對應的實體參數上註解@ModelAttribute,相應的實體會被最終傳遞到對應的view資源。
第二,重定向到某個頁面,只須要在返回的結果字符串前加上「redirect:」便可。
第三,ReflectionToStringBuilder.toString是org.apache.commons.lang.builder.ReflectionToStringBuilder中的方法,固然,添加相應的POM座標也是必然的。詳細能夠參見往前本系列特別篇開始之後的博客文章。
像以前說的,因爲沒有修改Service中的實現,數據是模擬的,這裏就不把展現頁面截圖了。
這裏只把控制檯輸出:
表單提交後,接收請求的控制器方法doSave輸出的實體對象鍵值對(ReflectionToStringBuilder真好用啊)。
重定向以後的輸出日誌。
好,最好我把本篇的控制器類完整給出:
package com.happyBKs.controller; import com.happyBKs.model.Course; import com.happyBKs.service.CourseService; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * Created by happyBKs on 2016/6/15. */ @Controller @RequestMapping("/courses") // /courses/** public class CourseController { //完成日誌信息 private static Logger log= LoggerFactory.getLogger(CourseController.class); private CourseService courseService; //使用spring容器管理裏了對應的依賴關係 @Autowired public void setCourseService(CourseService courseService) { this.courseService = courseService; } //提供完成一個業務的方法:根據課程ID查詢課程內容。 //本方法將處理 /courses/view?courseId=123 形式的URL @RequestMapping(value="/view", method= RequestMethod.GET) public String viewCourse(@RequestParam("courseId") Integer courseId, Model model) { //日誌輸出,查看請求的courseId是否是咱們的courseId log.info("In viewCourse, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.addAttribute(course); return "course_overview"; } //本方法將處理 /courses/view2/123 形式的URL @RequestMapping("/view2/{courseId}") public String viewCourse2(@PathVariable("courseId") Integer courseId, Map<String, Object> model) { log.info("In viewCourse2, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); model.put("course",course); return "course_overview"; } //本方法將處理 /courses/view3?courseId=123 形式的URL @RequestMapping("/view3") public String viewCourse3(HttpServletRequest request) { Integer courseId = Integer.valueOf(request.getParameter("courseId")); log.info("In viewCourse3, courseId = {}", courseId); Course course = courseService.getCoursebyId(courseId); request.setAttribute("course",course); return "course_overview"; } @RequestMapping(value="/admin", method = RequestMethod.GET, params = "add") public String createCourse(){ return "course_admin/edit"; } @RequestMapping(value="/save", method = RequestMethod.POST) public String doSave(@ModelAttribute Course course){ log.info("Info of Course:"); log.info(ReflectionToStringBuilder.toString(course)); //在此進行業務操做,好比數據庫持久化 course.setCourseId(123); return "redirect:view2/"+course.getCourseId(); } }