- Accept request parameter
- Spring MVC允許多種方式將client的data傳到controller method中
- Query parameter
- Form parameter
- Path variable
- 處理Query parameter
- 翻頁功能,讓使用者可以選擇要看哪一頁
- 假設要查看某一頁的Spittle列表,則這個list會按照最新的Spittle在前進行排序,因此下一頁的第一筆ID早於前一頁的最後一筆ID
- 為了顯示下一頁的Spittle,需要將一個Spittle的ID傳入
- 也可以傳入一個parameter確定要展現的Spittle counts
- 需要寫的controller method要處理以下參數
- before parameter
- count parameter
- 將spittles() method替換為使用上述兩個parameter的新spittles() method
- 先寫測試:
- 這個測試傳入parameters
- 測試了這個parameter存在時的controller method
- 新的spittles controller (同時處理有參數及沒有參數時的情境)
- 從path parameter中取得input
- 最簡單的方法就是在controller method中加上@RequestParam("spittle_id")
- 這個method可以處理類似/spittles/show?spittle_id=12345的request
- 從resource-orientation 角度來看,這個方法不好。/spittles/12345比/spittles/show?spittle_id=12345來得好
- /spittles/12345: 能夠識別要查詢的resource
- /spittles/show?spittle_id=12345: 只是一個有parameter的操作
- 以下是一個測試:
- 如果要處理例如spittle_id=12345的request,可以使用placeholder。
- 使用@PathVariable表示不管placeholder的值是什麼,都會傳到spittleId的parameter中
- 若@RequestMapping內的value name與@PathVariable相同(這邊都是spittleId),則@PathVariable內名稱可以省略
- 也就是如果@PathVariable裡面沒有值,會假設與placeholder parameter name相同
- 傳到model的key是spittle
- 如此,就能加上jsp 取得內容及時間
- 如果傳送的data是少量得,則Query parameters and path parameters是合理的。但通常都會傳送大量data,那麼使用Query parameters就不是個好選擇
- Processing forms
- 新建一個SpitterController來接受request展現registerForm
- 兩個@RequestMapping要組合起來,所以變成處理/spitter/register的request
- 測試,確認view name是對的
- JSP:
- Form內沒有action,因此會當表單提交時,會直接提交到與當時的URL相同的path上
- form-handling controller
- 當form submit發出post request後,controller需要接受form data且將之保存為Spitter object。且為了避免重複提交,browser 應該要重新到嚮導新的頁面
- controller
- 測試:
- 建立提交表單後的controller
- 當看到redirect: 前綴時,就知道要重新定向。假如spitter.getUsername()是miii,則會傳送到/spitter/miii
- 還可以使用forward前綴,當發現是forward,就會前往指定的URL path
- 接著就新增method到controller,針對後綴有username的做處理
- profile view:
- 問題:應該要為表單提交進行validation,以避免值為空或太長
- Validating forms
- 要處理username/password是空的情況,否則會出現安全問題,不管是誰只要提交空表單就能register
- 方法可以是保持value在合理的長度範圍內,避免這些input field的誤用
- 可以是把邏輯放在controller程式碼內。但,與其讓校驗邏輯弄亂controller method,不如使用Spring對Java validation API(JSP-303)。
- 從Spring3.0開始,在Spring只要使用Java validation API,不需要額為配置,只要保證在class path下有這個Java API,比如Hibernate Validator
- 只要將annotation放到property上,從而限制這些property的值
- annotation都在javax.validation.constrains package中
- Java提供validation annotation
- @AssertFalse
- @AssertTrue
- @DecimalMax: 值要<=給定的BigDecimalString
- @DecimalMin: 值要>=給定的BigDecimalString
- @Digits: 有指定的位數
- @Future: 需要是將來的日期
- @Max: 值要<=給定的值
- @Min: 值要>=給定的值
- @NotNull
- @Null
- @Past: 需要是過去的日期
- @Pattern: 直要是給訂的正規表示法
- @Size: 值必須是String, collection或data,且給定的長度要符合給定的範圍
- 範例:
- 開啟validate,修改controller的method processRegistration
- 重要:Errors parameter要緊跟著帶有@Valid annotation的parameter後面
@Test public void shouldShowPagedSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(50); SpittleRepository mockRepository = mock(SpittleRepository.class); when(mockRepository.findSpittles(238900, 50)) // expected的max及count parameter .thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")) .build(); mockMvc.perform(get("/spittles?max=238900&count=50")) // 傳入的參數 .andExpect(view().name("spittles")) .andExpect(model().attributeExists("spittleList")) .andExpect(model().attribute("spittleList", hasItems(expectedSpittles.toArray()))); }
@RequestMapping(method=RequestMethod.GET) public List<Spittle> spittles( @RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max, @RequestParam(value="count", defaultValue="20") int count) { return spittleRepository.findSpittles(max, count); }
@RequestMapping(value = "/show", method = RequestMethod.GET) public String showSpittle(@RequestParam("spittle_id") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
@Test public void testSpittle() throws Exception { Spittle expectedSpittle = new Spittle("Hello", new Date()); //expected SpittleRepository mockRepository = mock(SpittleRepository.class); when(mockRepository.findOne(12345)).thenReturn(expectedSpittle); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spittles/12345")).andExpect(view().name("spittle")) // 透過路徑取得資源 .andExpect(model().attributeExists("spittle")) .andExpect(model().attribute("spittle", expectedSpittle)); }
@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET) public String spittle(@PathVariable("spittleId") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET) public String spittle(@PathVariable long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
<div class="spittleView"> <div class="spittleMessage"><c:out value="${spittle.message}" /></div> <div> <span class="spittleTime"><c:out value="${spittle.time}" /></span> </div> </div>
@Controller @RequestMapping("/spitter") public class SpitterController { @RequestMapping(value = "/register", method = GET) public String showRegistrationForm() { return "registerForm"; } }
@Test public void shouldShowRegistration() throws Exception { SpitterController controller = new SpitterController(); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spitter/register")) .andExpect(view().name("registerForm")); }
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ page session="false"%> <html> <head> <title>Spittr</title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />"> </head> <body> <h1>Register</h1> <form method="POST"> First Name: <input type="text" name="firstName" /><br /> Last Name: <input type="text" name="lastName" /><br /> Username: <input type="text" name="username" /><br /> Password: <input type="password" name="password" /><br /> <input type="submit" value="Register" /> </form> </body> </html>
@RequestMapping(method=RequestMethod.POST) public String saveSpittle(SpittleForm form, Model model) throws Exception { spittleRepository.save(new Spittle(null, form.getMessage(), new Date(), form.getLongitude(), form.getLatitude())); return "redirect:/spittles"; }
@Test public void shouldProcessRegistration() throws Exception { SpitterRepository mockRepository = mock(SpitterRepository.class); Spitter unsaved = new Spitter("jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov"); Spitter saved = new Spitter(24L, "jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov"); // return ID when(mockRepository.save(unsaved)).thenReturn(saved); SpitterController controller = new SpitterController(mockRepository); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(post("/spitter/register").param("firstName", "Jack").param("lastName", "Bauer") .param("username", "jbauer").param("password", "24hours").param("email", "jbauer@ctu.gov")) .andExpect(redirectedUrl("/spitter/jbauer")); verify(mockRepository, atLeastOnce()).save(unsaved); // 確認save狀況 }
package spittr.web; import static org.springframework.web.bind.annotation.RequestMethod.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import spittr.Spitter; import spittr.data.SpitterRepository; @Controller @RequestMapping("/spitter") public class SpitterController { private SpitterRepository spitterRepository; @Autowired public SpitterController(SpitterRepository spitterRepository) { this.spitterRepository = spitterRepository; } @RequestMapping(value = "/register", method = GET) public String showRegistrationForm() { return "registerForm"; } @RequestMapping(value = "/register", method = POST) public String processRegistration(Spitter spitter) { // 接受一個spitter當作parameter spitterRepository.save(spitter); // spitterRepository是被injects的parameter return "redirect:/spitter/" + spitter.getUsername(); } }
@RequestMapping(value = "/{username}", method = GET) public String showSpitterProfile(@PathVariable String username, Model model) { Spitter spitter = spitterRepository.findByUsername(username); model.addAttribute(spitter); return "profile"; }
<h1>Your Profile</h1> <c:out value="${spitter.username}" /><br/> <c:out value="${spitter.firstName}" /> <c:out value="${spitter.lastName}" />
package spittr; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; Annotation Description public class Spitter { private Long id; @NotNull @Size(min=5, max=16) // 不能是空的,且5-16個字 private String username; @NotNull @Size(min=5, max=25) // 不能是空的,且5-25個字 private String password; @NotNull @Size(min=2, max=30) // 不能是空的,且2-30個字 private String firstName; @NotNull @Size(min=2, max=30) // 不能是空的,且2-30個字 private String lastName; ... }
@RequestMapping(value = "/register", method = POST) public String processRegistration(@Valid Spitter spitter, Errors errors) { if (errors.hasErrors()) { // 驗證有錯誤的時候return回registerForm return "registerForm"; } spitterRepository.save(spitter); return "redirect:/spitter/" + spitter.getUsername(); }
沒有留言:
張貼留言