三氮趋、接受請(qǐng)求的輸入
在Spring MVC
中,允許以多種方式將客戶端中的數(shù)據(jù)傳送到控制器的處理方法中江耀,包括:
- 查詢參數(shù)
- 表單參數(shù)
- 路徑變量
3.1 處理查詢參數(shù)
如果我們向讓用戶每次都能產(chǎn)看某一頁(yè)的Spittle
歷史剩胁,那么就需要提供一種方式讓用戶傳遞參數(shù)進(jìn)來(lái),進(jìn)而確定要展現(xiàn)哪些Spittle
集合祥国。在確定該如何實(shí)現(xiàn)時(shí)昵观,假設(shè)我們要查看某一頁(yè)Spittle
列表,這個(gè)列表會(huì)按照最新的Spittle
在前的方式進(jìn)行排序(最后發(fā)布是Spittle
消息的ID
最大)舌稀。因此啊犬,下一頁(yè)中第一條的ID
肯定會(huì)早于當(dāng)前頁(yè)最后一條的ID
。因此壁查,為了顯示下一頁(yè)的Spittle
觉至,需要將一個(gè)Spittle
的ID
傳入進(jìn)來(lái),這個(gè)ID
要恰好小于當(dāng)前頁(yè)最后一條Spittle
的ID
睡腿。另外语御,還可以傳入一個(gè)參數(shù)確定要展現(xiàn)的Spittle
數(shù)量峻贮。
為了實(shí)現(xiàn)這個(gè)分頁(yè)的功能,所編寫(xiě)的處理器方法要接受如下的參數(shù):
-
max
參數(shù)(標(biāo)明結(jié)果中所有的Spittle
的ID
均應(yīng)該在這個(gè)值之前) -
count
參數(shù)(表明要查詢的Spittle
數(shù)量)
下面給出SpittleController
控制器中新的spittles()
方法:
@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);
}
說(shuō)明:可以看到這里max
參數(shù)的默認(rèn)值是MAX_LONG_AS_STRING
(因?yàn)椴樵儏?shù)都是String
類型的应闯,即通過(guò)GET
方式傳遞參數(shù)都是String
類型的纤控,因此這里不能使用Long.MAX_VALUE
,當(dāng)MAX_LONG_AS_STRING
綁定到max
的時(shí)候會(huì)轉(zhuǎn)換成Long
類型)碉纺,即最大值嚼黔,也就是說(shuō)所有的Spittle
消息的ID
都不可能大于此數(shù)。再次對(duì)其進(jìn)行測(cè)試:
@Test
public void shouldShowPagedSpittles() throws Exception {
List<Spittle> expectedSpittles = createSpittleList(50);
SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
Mockito.when(mockRepository.findSpittles(238900, 50))
.thenReturn(expectedSpittles);
SpittleController controller = new SpittleController(mockRepository);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/spittles?max=238900&count=50"))
.andExpect(MockMvcResultMatchers.view().name("spittles"))
.andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))
.andExpect(MockMvcResultMatchers.model().attribute("spittleList",
org.hamcrest.Matchers.hasItems(expectedSpittles.toArray())));
}
3.2 通過(guò)路徑參數(shù)接受輸入
假設(shè)應(yīng)用程序需要根據(jù)給定的ID
來(lái)展現(xiàn)某一個(gè)Spittle
記錄惜辑。其中一種方案就是編寫(xiě)處理器方法唬涧,通過(guò)使用@RequestParam
注解,讓它接受ID
作為查詢參數(shù):
@RequestMapping(value="/show", method=RequestMethod.GET)
public String spittle(
@PathVariable("spittle_id") long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
說(shuō)明:這個(gè)處理器方法將會(huì)處理形如“/spittles/show?spittle_id=12345”
這樣的請(qǐng)求盛撑。這樣可以正常工作碎节,但是從面向資源的角度來(lái)看這并不理想。在理想情況下抵卫,要識(shí)別的資源應(yīng)該通過(guò)URL
路徑進(jìn)行標(biāo)識(shí)狮荔,而不是通過(guò)查詢參數(shù)。對(duì)“spittles/12345”
發(fā)起GET
請(qǐng)求要優(yōu)于對(duì)“/spittles/show?spittle_id=12345”
發(fā)起請(qǐng)求介粘。
下面我們給出新的測(cè)試方法殖氏,它會(huì)斷言SpittleController
中對(duì)面向資源的請(qǐng)求的處理。
@Test
public void testSpittle() throws Exception {
Spittle expectedSpittle = new Spittle("Hello", new Date());
SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
Mockito.when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);
SpittleController controller = new SpittleController(mockRepository);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(MockMvcRequestBuilders.get("/spittles/12345"))
.andExpect(MockMvcResultMatchers.view().name("spittle"))
.andExpect(MockMvcResultMatchers.model().attributeExists("spittle"))
.andExpect(MockMvcResultMatchers.model().attribute("spittle", expectedSpittle));
}
說(shuō)明:可以看到這里使用地址“/spittles/12345”
進(jìn)行測(cè)試姻采,如果向讓測(cè)試通過(guò)雅采,需要編寫(xiě)@RequestParam
要包含變量部分,這部分代表SpittleID
慨亲。下面給出新的spittle()
方法:
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
@PathVariable("spittleId") long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
說(shuō)明:這里在@RequestMapping
中使用了占位符spittleId
婚瓜,而方法參數(shù)中使用@PathVariable
標(biāo)識(shí),這標(biāo)明不管占位符的名字(這里是spittleId
)是什么刑棵,都會(huì)傳遞到處理器方法的spittleId
參數(shù)中巴刻。當(dāng)然這里占位符和方法參數(shù)名一致,可以去掉@PathVariable
中的值蛉签。下面給出spittle.jsp
:
<div class="spittleView">
<div class="spittleMessage"><c:out value="${spittle.message}" /></div>
<div>
<span class="spittleTime"><c:out value="${spittle.time}" /></span>
</div>
</div>
四胡陪、處理表單
使用表單分為兩個(gè)方面:展現(xiàn)表單以及處理用戶通過(guò)表單提交的數(shù)據(jù)。在本應(yīng)用中需要有各表單讓新用戶進(jìn)行注冊(cè)碍舍。
package spittr.web;
import org.springframework.web.bind.annotation.RequestMethod;
import ...;
@Controller
@RequestMapping("/spitter")
public class SpitterController {
@RequestMapping(value="/register", method=RequestMethod.GET)
public String showRegistrationForm() {
return "registerForm";
}
}
說(shuō)明:這個(gè)方法非常簡(jiǎn)單柠座,就是向應(yīng)用發(fā)起請(qǐng)求,返回一個(gè)表單頁(yè)面乒验,相關(guān)測(cè)試如下:
@Test
public void shouldShowRegistration() throws Exception {
SpitterRepository mockRepository = Mockito.mock(SpitterRepository.class);
SpitterController controller = new SpitterController(mockRepository);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(MockMvcRequestBuilders.get("/spitter/register"))
.andExpect(MockMvcResultMatchers.view().name("registerForm"));
}
說(shuō)明:這里斷言視圖為“registerForm”
愚隧。下面給出注冊(cè)表單registerForm.jsp
:
<form method="POST">
First Name: <input type="text" name="firstName" /><br/>
Last Name: <input type="text" name="lastName" /><br/>
Email: <input type="email" name="email" /><br/>
Username: <input type="text" name="username" /><br/>
Password: <input type="password" name="password" /><br/>
<input type="submit" value="Register" />
</form>
說(shuō)明:這里的<form>
標(biāo)簽中并沒(méi)有設(shè)置action
屬性蒂阱。此時(shí)锻全,它會(huì)提交到與展現(xiàn)時(shí)相同的URL
路徑上(即GET
請(qǐng)求地址“/spitter/register”
)狂塘。當(dāng)表單填寫(xiě)無(wú)誤提交后,需要在服務(wù)器端處理該HTTP POST
請(qǐng)求鳄厌。
4.1 編寫(xiě)處理表單的控制器
當(dāng)處理注冊(cè)表單的POST
請(qǐng)求時(shí)荞胡,控制器需要接受表單數(shù)據(jù)并將表單數(shù)據(jù)保存為Spitter
對(duì)象。最后了嚎,為了防止重復(fù)提交泪漂,應(yīng)該將瀏覽器重定向到新創(chuàng)建用戶的基本信息頁(yè)面。下面先給出測(cè)試代碼:
@Test
public void shouldProcessRegistration() throws Exception {
SpitterRepository mockRepository = Mockito.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");
Mockito.when(mockRepository.save(unsaved)).thenReturn(saved);
SpitterController controller = new SpitterController(mockRepository);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(MockMvcRequestBuilders.post("/spitter/register")
.param("firstName", "Jack")
.param("lastName", "Bauer")
.param("username", "jbauer")
.param("password", "24hours")
.param("email", "jbauer@ctu.gov"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/jbauer"));
Mockito.verify(mockRepository, Mockito.atLeastOnce()).save(unsaved);
}
說(shuō)明:這里首先構(gòu)建了兩個(gè)相同Spitter
對(duì)象(具體代碼后面會(huì)給出)歪泳,只是一個(gè)有ID
(這個(gè)ID
是隨意加的)萝勤,一個(gè)沒(méi)有。然后發(fā)起注冊(cè)請(qǐng)求呐伞,斷言至少保存了一個(gè)Spitter
對(duì)象敌卓。在處理POST
請(qǐng)求完后,為防止重復(fù)提交伶氢,最好進(jìn)行一下重定向趟径,這里重定向到此對(duì)象的顯示頁(yè)面(“/spitter/jbauer”
)中。給出修改后的控制器SpitterController :
package spittr.web;
import ...
@Controller
@RequestMapping("/spitter")
public class SpitterController {
private SpitterRepository spitterRepository;
@Autowired
public SpitterController(SpitterRepository spitterRepository) {
this.spitterRepository = spitterRepository;
}
@RequestMapping(value="/register", method=RequestMethod.GET)
public String showRegistrationForm() {
return "registerForm";
}
@RequestMapping(value="/register", method=RequestMethod.POST)
public String processRegistration(Spitter spitter) {
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}
}
說(shuō)明:控制器在處理完注冊(cè)請(qǐng)求之后癣防,對(duì)訪問(wèn)進(jìn)行了重定向蜗巧,這里使用“redirect:”
進(jìn)行重定向。當(dāng)然我們還可以使用“forward:”
進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)蕾盯。這里進(jìn)行請(qǐng)求重定向之后幕屹,我們?cè)诳刂破髦羞€需要針對(duì)此重定向的請(qǐng)求進(jìn)行處理:
@RequestMapping(value="/{username}", method=RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
Spitter spitter = spitterRepository.findByUsername(username);
model.addAttribute(spitter);
return "profile";
}
說(shuō)明:下面給出相關(guān)的profile.jsp
:
<h1>Your Profile</h1>
<c:out value="${spitter.username}" /><br/>
<c:out value="${spitter.firstName}" /> <c:out value="${spitter.lastName}" /><br/>
<c:out value="${spitter.email}" />
4.2 校驗(yàn)表單
在填寫(xiě)表單的時(shí)候,一般需要對(duì)填寫(xiě)的字段進(jìn)行校驗(yàn)级遭。但是手動(dòng)為每個(gè)字段編寫(xiě)校驗(yàn)顯然比較麻煩香嗓。從Spring 3.0
開(kāi)始,在Spring MVC
中提供了對(duì)Java
校驗(yàn)的API
支持装畅。這里需要導(dǎo)入兩個(gè)額外的包:hibernate-validator-6.0.0.Alpha2.jar靠娱、validation-api-2.0.0.Alpha2.jar
。
Java
校驗(yàn)API
所提供的校驗(yàn)注解
注解 | 描述 |
---|---|
@AssertFalse |
所注解的元素必須是Boolean 類型掠兄,并且值為false
|
@AssertTrue |
所注解的元素必須是Boolean 類型像云,并且值為true
|
@DecimalMax |
所注解的元素必須是數(shù)字,并且它的值要小于或等于給定的BigDecimalString 值 |
@DecimalMin |
所注解的元素必須是數(shù)字蚂夕,并且它的值要大于或等于給定的BigDecimalString 值 |
@Digits |
所注解的元素必須是數(shù)字迅诬,并且它的值必須有指定的位數(shù) |
@Future |
所注解的元素必須是一個(gè)將來(lái)的日期 |
@Max |
所注解的元素必須是數(shù)字,并且它的值要小于或等于給定的值 |
@Min |
所注解的元素必須是數(shù)字婿牍,并且它的值要大于或等于給定的值 |
@NotNull |
所注解的元素的值必須不能為null
|
@Null |
所注解的元素的值必須為null
|
@Past |
所注解的元素的值必須是一個(gè)已過(guò)去的日期 |
@Pattern |
所注解的元素的值必須匹配給定的正則表達(dá)式 |
@Size |
所注解的元素的值必須是String 侈贷、集合或數(shù)組,并且它的長(zhǎng)度要符合給定的范圍 |
下面給出spitter
類:
package spittr;
import javax.validation.constraints.*;
import org.apache.commons.lang3.builder.*;
import org.hibernate.validator.constraints.Email;
public class Spitter {
private Long id;
@NotNull//非空等脂,5到16各字符
@Size(min=5, max=16)
private String username;
@NotNull
@Size(min=5, max=25)
private String password;
@NotNull
@Size(min=2, max=30)
private String firstName;
@NotNull
@Size(min=2, max=30)
private String lastName;
@NotNull
@Email
private String email;
public Spitter() {}
public Spitter(String username, String password, String firstName, String lastName, String email) {
this(null, username, password, firstName, lastName, email);
}
public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {
this.id = id;
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
//getter俏蛮、setter方法省略
@Override
public boolean equals(Object that) {
return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");
}
}
說(shuō)明:現(xiàn)在已經(jīng)為spitter
添加了校驗(yàn)注解撑蚌,接下來(lái)需要修改控制器中的注冊(cè)方法processRegistration()
方法來(lái)應(yīng)用校驗(yàn)功能。
@RequestMapping(value="/register", method=RequestMethod.POST)
public String processRegistration(@Valid Spitter spitter, Errors errors) {
if (errors.hasErrors()) {
return "registerForm";
}
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}
說(shuō)明:這里使用Spring
的校驗(yàn)注解@Valid
來(lái)對(duì)Spitter
輸入進(jìn)行校驗(yàn)搏屑,如果校驗(yàn)出現(xiàn)錯(cuò)誤争涌,則重新放安徽表單。注意辣恋,在Spitter
屬性上添加校驗(yàn)限制并不能組織表單提交亮垫。在填寫(xiě)出現(xiàn)錯(cuò)誤時(shí),需要讓相關(guān)錯(cuò)誤信息顯示在表單頁(yè)面(下一節(jié)進(jìn)行講解)伟骨。