三、接受请求的输入
在Spring MVC
中,允许以多种方式将客户端中的数据传送到控制器的处理方法中,包括:
- 查询参数
- 表单参数
- 路径变量
3.1 处理查询参数
如果我们向让用户每次都能产看某一页的Spittle
历史,那么就需要提供一种方式让用户传递参数进来,进而确定要展现哪些Spittle
集合。在确定该如何实现时,假设我们要查看某一页Spittle
列表,这个列表会按照最新的Spittle
在前的方式进行排序(最后发布是Spittle
消息的ID
最大)。因此,下一页中第一条的ID
肯定会早于当前页最后一条的ID
。因此,为了显示下一页的Spittle
,需要将一个Spittle
的ID
传入进来,这个ID
要恰好小于当前页最后一条Spittle
的ID
。另外,还可以传入一个参数确定要展现的Spittle
数量。
为了实现这个分页的功能,所编写的处理器方法要接受如下的参数:
-
max
参数(标明结果中所有的Spittle
的ID
均应该在这个值之前) -
count
参数(表明要查询的Spittle
数量)
下面给出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);
}
说明:可以看到这里max
参数的默认值是MAX_LONG_AS_STRING
(因为查询参数都是String
类型的,即通过GET
方式传递参数都是String
类型的,因此这里不能使用Long.MAX_VALUE
,当MAX_LONG_AS_STRING
绑定到max
的时候会转换成Long
类型),即最大值,也就是说所有的Spittle
消息的ID
都不可能大于此数。再次对其进行测试:
@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 通过路径参数接受输入
假设应用程序需要根据给定的ID
来展现某一个Spittle
记录。其中一种方案就是编写处理器方法,通过使用@RequestParam
注解,让它接受ID
作为查询参数:
@RequestMapping(value="/show", method=RequestMethod.GET)
public String spittle(
@PathVariable("spittle_id") long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
说明:这个处理器方法将会处理形如“/spittles/show?spittle_id=12345”
这样的请求。这样可以正常工作,但是从面向资源的角度来看这并不理想。在理想情况下,要识别的资源应该通过URL
路径进行标识,而不是通过查询参数。对“spittles/12345”
发起GET
请求要优于对“/spittles/show?spittle_id=12345”
发起请求。
下面我们给出新的测试方法,它会断言SpittleController
中对面向资源的请求的处理。
@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));
}
说明:可以看到这里使用地址“/spittles/12345”
进行测试,如果向让测试通过,需要编写@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";
}
说明:这里在@RequestMapping
中使用了占位符spittleId
,而方法参数中使用@PathVariable
标识,这标明不管占位符的名字(这里是spittleId
)是什么,都会传递到处理器方法的spittleId
参数中。当然这里占位符和方法参数名一致,可以去掉@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>
四、处理表单
使用表单分为两个方面:展现表单以及处理用户通过表单提交的数据。在本应用中需要有各表单让新用户进行注册。
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";
}
}
说明:这个方法非常简单,就是向应用发起请求,返回一个表单页面,相关测试如下:
@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"));
}
说明:这里断言视图为“registerForm”
。下面给出注册表单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>
说明:这里的<form>
标签中并没有设置action
属性。此时,它会提交到与展现时相同的URL
路径上(即GET
请求地址“/spitter/register”
)。当表单填写无误提交后,需要在服务器端处理该HTTP POST
请求。
4.1 编写处理表单的控制器
当处理注册表单的POST
请求时,控制器需要接受表单数据并将表单数据保存为Spitter
对象。最后,为了防止重复提交,应该将浏览器重定向到新创建用户的基本信息页面。下面先给出测试代码:
@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);
}
说明:这里首先构建了两个相同Spitter
对象(具体代码后面会给出),只是一个有ID
(这个ID
是随意加的),一个没有。然后发起注册请求,断言至少保存了一个Spitter
对象。在处理POST
请求完后,为防止重复提交,最好进行一下重定向,这里重定向到此对象的显示页面(“/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();
}
}
说明:控制器在处理完注册请求之后,对访问进行了重定向,这里使用“redirect:”
进行重定向。当然我们还可以使用“forward:”
进行请求转发。这里进行请求重定向之后,我们在控制器中还需要针对此重定向的请求进行处理:
@RequestMapping(value="/{username}", method=RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
Spitter spitter = spitterRepository.findByUsername(username);
model.addAttribute(spitter);
return "profile";
}
说明:下面给出相关的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 校验表单
在填写表单的时候,一般需要对填写的字段进行校验。但是手动为每个字段编写校验显然比较麻烦。从Spring 3.0
开始,在Spring MVC
中提供了对Java
校验的API
支持。这里需要导入两个额外的包:hibernate-validator-6.0.0.Alpha2.jar、validation-api-2.0.0.Alpha2.jar
。
Java
校验API
所提供的校验注解
注解 | 描述 |
---|---|
@AssertFalse |
所注解的元素必须是Boolean 类型,并且值为false
|
@AssertTrue |
所注解的元素必须是Boolean 类型,并且值为true
|
@DecimalMax |
所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString 值 |
@DecimalMin |
所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString 值 |
@Digits |
所注解的元素必须是数字,并且它的值必须有指定的位数 |
@Future |
所注解的元素必须是一个将来的日期 |
@Max |
所注解的元素必须是数字,并且它的值要小于或等于给定的值 |
@Min |
所注解的元素必须是数字,并且它的值要大于或等于给定的值 |
@NotNull |
所注解的元素的值必须不能为null
|
@Null |
所注解的元素的值必须为null
|
@Past |
所注解的元素的值必须是一个已过去的日期 |
@Pattern |
所注解的元素的值必须匹配给定的正则表达式 |
@Size |
所注解的元素的值必须是String 、集合或数组,并且它的长度要符合给定的范围 |
下面给出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");
}
}
说明:现在已经为spitter
添加了校验注解,接下来需要修改控制器中的注册方法processRegistration()
方法来应用校验功能。
@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();
}
说明:这里使用Spring
的校验注解@Valid
来对Spitter
输入进行校验,如果校验出现错误,则重新放安徽表单。注意,在Spitter
属性上添加校验限制并不能组织表单提交。在填写出现错误时,需要让相关错误信息显示在表单页面(下一节进行讲解)。
网友评论