平常都在用Spring+REST api的設計,在參數的傳遞也習慣用JSON,突然間最基本的Spring+Servlet+JSP的寫法反而忘記,趕緊來筆記一下。
✦ Controller裡面的 HttpServletRequest request:
request.getParameter() 一般是指Client透過URL Query傳值到Server。一般只能傳送String。
http://www.ajoshow.com/servlet?parameter=1
request.getAttribute() 一般用於Server端取值,例如從Servlet設值,JSP取值,可以傳遞的值不限於String,也可以是物件。
✦ 在Spring MVC中,三個主要傳遞值用的物件為Model、ModelMap、ModelAndView。
三個主要的作用都像request.setAttribute(),唯不同的是Model是interface;ModelMap是class,有點像Map加強版,也是把值最後傳遞至View上;最後ModelAndView則較像是ModelMap和View的容器,方便把設值跟喧染畫面放在同一個回傳。
@RequestMapping(...) public String sample(Model model) { model.addAttribute("message", "Hello World!!"); return "hello"; } @RequestMapping(...) public String sample2(ModelMap modelMap) { modelMap.addAttribute("message", "Hello World!!"); return "hello"; } @RequestMapping(...) public ModelAndView sample3(ModelMap modelMap) { return new ModelAndView("hello", "message", "Hello World!!"); }
✦ 頁面跳轉有兩種方式redirect、forward。
redirect:伺服器通知客戶端,讓其重新請求一次。其原本參數狀態不被保留。
forward:伺服器端內部重新定向。通常是導向至JSP (View)。參數狀態會被傳遞。
可以透過HttpServletRequest、HttpServletResponse達到上述動作。
redirect方式:
response.sendRedirect("/page.jsp"); // HttpServletResponse
forward方式:
request.getRequestDispatcher("/page.jsp").forward(request, response);
在Controller導頁也可以下列方式實現:
return new ModelAndView("redirect:/index"); // or return "redirect:/index";
✦ 如果要帶參數一個方式是直接加在URL後面如redirect:/index?param=hello。或是可以透過RedirectAttributes來做到導頁參數傳遞:
@RequestMapping(...) public String sample(RedirectAttributes redirectAttributes) { redirectAttributes.addAttribute("param", "hello"); return "redirect:/index"; }
你會發現不管使用哪種方式,最後頁面上的URL都會有?param=hello在後面。
如果參數是想要從某一頁面導到index JSP上使用,例如${attr},那麼可以使用RedirectAttributes.addFlashAttribute()來實現。該參數利用FlashMap暫存在Session裡面,導向至該頁面後將被予刪除。
@RequestMapping(...) public String sample(RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute("attr", "world"); return "redirect:/index"; }
再來一個example:
@RequestMapping("/page1") private String page1(@RequestParam String param1, RedirectAttributes ra) { ra.addAttribute("param1", param1); ra.addFlashAttribute("attr1", "attr1"); System.out.println(param1); return "redirect:/page2"; } @RequestMapping("/page2") private ModelAndView page2(@RequestParam String param1, @ModelAttribute("attr1") String attr1) { System.out.println(param1); System.out.println(attr1); return new ModelAndView("/page2"); }
我們可以透過@RequestParam來接url後面的參數,而用@ModelAttribute來接伺服器端設置的attribute。這邊需要注意的部分是@ModelAttribute有特別帶”attr1″,因為預設name會是宣告類型的class name。
上述提到ModelAndView也可以實現導頁,而Model當然也可以取得到透過addFlashAttribute()裡面的值:
model.asMap().get("attr1");
✦ 另一種導頁方式是透過回傳RedirectView。使用這個物件提供一些便於設定url parameters和導址的方法,而其中一個值得注意的是setContextRelative()。假設有一個網址為http://www.ajoshow.com/app/index,當這個值為true時,會以application context為首,也就是/app/{your_path};為false時,那就是以host為root位置,也就是 /{your_path}。一般在同一個內部應用來說,預設為false,建議設定為true。
@RequestMapping(...) public RedirectView save(RedirectAttributes redirectAttrs) { redirectAttrs.addAttribute("msg", "Hello World!"); redirectAttrs.addFlashAttribute("attr1", "attr1"); RedirectView redirectView = new RedirectView(); redirectView.setContextRelative(true); redirectView.setUrl("/page/{msg}"); return redirectView; }
RedirectView雖然在設定url parameter挺方便的,也提供url placeholder使用,但如果要傳遞attribute時,可能還是需要RedirectAttributes的輔助。RedirectView在某一層面上必須讓Controller綁定回傳RedirectView物件,如果突然要取消導頁,這樣的綁定回傳其實並不太友善,反而是透過回傳ModelAndView的方式會相對彈性許多。
@RequestMapping(...) private ModelAndView sample() { return new ModelAndView(new RedirectView("/page2", true), "attr1", "attr1"); }
✦ 基本的參數傳遞這邊先告一個段落,接著說說參數綁定,可以透過下列幾個annotation實現:@RequestHeader、@CookieValue、@PathVariable、@RequestParam、@RequestBody、@SessionAttributes、@SessionAttribute、@ModelAttribute、@RequestAttribute。
@RequestHeader :取Header的值
/* Sample header * * Host localhost:8080 * Accept-Encoding gzip,deflate * Keep-Alive 300 */ @RequestMapping(...) public void getHeader(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive)
@CookieValue:取Header中Cookie參數中各個項目的值
/* Sample cookie * * JSESSIONID=678A4AC47B213DCBE0B2A1AA689CBC84 */ @RequestMapping(...) public void getCookie(@CookieValue("JSESSIONID") String cookie)
@PathVariable:取URL路徑上的變數
@RequestMapping("/books/{bookId}) public void getPath(@PathVariable String bookId)
@RequestParam:取URL後面的參數,也可拿來取Post Body裡面data的值,適合用來處理簡單型別的綁定,一般用於取Content-Type為application/x-www-form-urlencoded的POST、GET。
@RequestBody:常用於取Content-Type為非application/x-www-form-urlencoded類型,通常如application/json 、application/xml等,複雜型別的綁定,透過HttpMessageConverters解析Post data body,綁定到相對應的bean。
@SessionAttributes:綁定Session裡的attribute。
@Controller @RequestMapping(...) @SessionAttributes(value={"attr1","attr2"}) public class Demo { @RequestMapping("index") public ModelAndView index() { ModelAndView mav = new ModelAndView("page2"); mav.addObject("attr1", "attr1"); mav.addObject("attr2", "attr2"); return mav; } @RequestMapping("/page2") public ModelAndView page2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) { ModelAndView mav = new ModelAndView("end"); return mav; } }
*Scope 的層級可分為 application、session、request、page。上述程式碼在沒有用@SessionAttributes時attr1、attr2的scope是在request級別,有用時則會同步該參數至session裡面。如果要刪除該值,則在Controller裡面調入SessionStatus.setComplete()即可。
@RequestMapping(...) public ModelAndView sample(SessionStatus status) { status.setComplete(); return new ModelAndView(...); }
@SessionAttribute:從Session中取值。
@ModelAttribute:可用於方法上或是參數上。用於方法上會在呼叫@RequestMapping前調用,用於參數上則是拿來取session或request裡的attribute。
// Add one attribute // The return value of the method is added to the model under the name "account" // You can customize the name via @ModelAttribute("myAccount") @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } // Add multiple attributes @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountManager.findAccount(number)); // add more ... } // 參考: https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method
@RequestAttribute:新增於Spring 4.3,從 controller裡取request attribute。該attribute通常是在controller之前被設置,例如從filter或interceptor裡面的request.setAttribute後所傳遞的值。
✦ 綜合以上來個簡單的應用範例,嘗試傳遞多個Person的物件 。Post至page1 controller,自動產生5個Person物件並將陣列存放至People物件裡,導頁傳遞至page2 controller,再forward到page2 JSP,將剛剛產生的Person一一列出,該頁面可以再重新submit到page2 controller,然後於此取得到手動輸入的People物件,再重新forward至 page2 JSP。
public class People { private List<Person> persons; public List<Person> getPersons() { return persons; } public void setPersons(List<Person> persons) { this.persons = persons; } }
public class Person { private String firstname; private String lastname; private int age; public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Person{"); sb.append("firstname='").append(firstname).append('\''); sb.append(", lastname='").append(lastname).append('\''); sb.append(", age=").append(age); sb.append('}'); return sb.toString(); } }
// page2.jsp
<form:form method="post" action="/page2" modelAttribute="people"> <c:forEach varStatus="status" var="person" items="${ people.persons }"> <input type="text" name="persons[${status.index}].firstname" value="${person.firstname}" /> <input type="text" name="persons[${status.index}].lastname" value="${person.lastname}" /> <input type="text" name="persons[${status.index}].age" value="${person.age}" /> <br/> </c:forEach> <input type="submit" value="submit" /> </form:form>
// controller
@RequestMapping(value = "/page1") private ModelAndView page1(RedirectAttributes ra) { List<Person> persons = new ArrayList<>(); for(int i=0; i<5; i++){ Person person = new Person(); person.setFirstname("person" + i); person.setLastname("person" + i); person.setAge(i); persons.add(person); } People ppl = new People(); ppl.setPersons(persons); ra.addFlashAttribute("people", ppl); return new ModelAndView("redirect:/page2" ); } @RequestMapping(value = "/page2") private ModelAndView page2(@ModelAttribute("people") People people) { for(Person person : people.getPersons()){ System.out.println(person); } return new ModelAndView("/page2", "people", people); }
參考:
- https://stackoverflow.com/questions/5243754/difference-between-getattribute-and-getparameter
- https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/htmlsingle/#mvc-coc-modelmap
- https://www.concretepage.com/spring/spring-mvc/spring-mvc-redirectview-example-add-fetch-flash-attributes-redirectattributes-model-requestcontextutils
- https://stackoverflow.com/questions/14470111/spring-redirectattributes-addattribute-vs-addflashattribute
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/RedirectView.html
- https://blog.csdn.net/walkerjong/article/details/7946109
- https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-methods