使用表单分为两个方面:展现表单以及处理用户通过表单提交的数据。在Spittr应用中,我们需要有个表单让新用户进行注册。SpitterController是一个新的控制器,目前只有一个请求处理的方法来展现注册表单。
1.SpitterController.java 展现一个表单,允许用户注册该应用
1 package spittr.web; 2 3 import javax.validation.Valid; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.ui.Model; 8 import org.springframework.validation.Errors; 9 import org.springframework.web.bind.annotation.PathVariable; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.RequestMethod; 12 13 import spittr.data.SpitterRepository; 14 import spittr.spitter.Spitter; 15 16 @Controller 17 @RequestMapping("/spitter") 18 public class SpitterController { 19 20 @RequestMapping(value = "/register", method = RequestMethod.GET) // 处理对“/spitter/register”的GET请求 21 public String showRegistrationForm() { 22 return "registerForm"; 23 } 24 25 26 }
showRegistrationForm()方法的@RequestMapping注解以及 类级别上的@RequestMapping注解组合起来,声明了这个方法要处 理的是针对“/spitter/register”的GET请求。这是一个简单的方法,没有 任何输入并且只是返回名为registerForm的逻辑视图。按照我们 配置InternalResourceViewResolver的方式,这意味着将会使 用“/WEB-INF/ views/registerForm.jsp”这个JSP来渲染注册表单。
2.测试展现表单的控制器方法
1 @Test 2 public void shouldShowRegistration() throws Exception { 3 SpitterController controller = new SpitterController(); 4 MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); // 构建MockMvc 5 mockMvc.perform(get("/spitter/register")).andExpect(view().name("registerForm")); // 断言registerForm视图 6 }
这个测试方法与首页控制器的测试非常类似。它对“/spitter/register”发 送GET请求,然后断言结果的视图名为registerForm。
3.渲染注册表单的JSP registerForm.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 12 <title>Spitter</title> 13 <link rel="stylesheet" type="text/css" href="<c:url value="/respurces/style.css"/>"> 14 15 <meta http-equiv="pragma" content="no-cache"> 16 <meta http-equiv="cache-control" content="no-cache"> 17 <meta http-equiv="expires" content="0"> 18 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 19 <meta http-equiv="description" content="This is my page"> 20 <!-- 21 <link rel="stylesheet" type="text/css" href="styles.css"> 22 --> 23 24 </head> 25 26 <body> 27 <h1>Register</h1> 28 <form action="" method="POST"> 29 First Name:<input type="text" name="fistName"/><br> 30 Last Name:<input type="text" name="lastName"><br> 31 Username:<input type="text" name="username"><br> 32 Password:<input type="password" name="password"><br> 33 <input type="submit" value="Register"/> 34 </form> 35 </body> 36 </html>
需要注意的是:这里的<form>标签中并没有设置action属性。在这种情况下,当表单提交时,它会提交到与展现时相同的URL路径上。也就是说,它会提交到“/spitter/register”上。
4.在SpitterController中再添加一个方法来处理这个表单提交,即处理所提交的表单并注册新用户。
1 package spittr.web; 2 3 import javax.validation.Valid; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.ui.Model; 8 import org.springframework.validation.Errors; 9 import org.springframework.web.bind.annotation.PathVariable; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.RequestMethod; 12 13 import spittr.data.SpitterRepository; 14 import spittr.spitter.Spitter; 15 16 @Controller 17 @RequestMapping("/spitter") 18 public class SpitterController { 19 20 private SpitterRepository spitterRepository; 21 22 public SpitterController() { 23 24 } 25 26 @Autowired // 注入SpitterRepository 27 public SpitterController(SpitterRepository spitterRepository) { 28 this.spitterRepository = spitterRepository; 29 } 30 31 @RequestMapping(value = "/register", method = RequestMethod.GET) // 处理对“/spitter/register”的GET请求 32 public String showRegistrationForm() { 33 return "registerForm"; 34 } 35 36 @RequestMapping(value = "/register", method = RequestMethod.POST) 37 public String processRegistration(@Valid Spitter spitter, // 校验Spitter输入 38 Errors errors) { 39 if (errors.hasErrors()) { 40 return "registerForm"; // 如果校验出现错误,则重新返回表单 41 } 42 spitterRepository.save(spitter); //保存Spitter 43 return "redirect:/spitter/" + spitter.getUsername(); //重定向到基本信息页 44 } 45 46 @RequestMapping(value = "/{username}", method = RequestMethod.GET) 47 public String showSpitterProfile(@PathVariable String username, Model model) { 48 Spitter spitter = spitterRepository.findByUsername(username); 49 model.addAttribute(spitter); 50 return "profile"; 51 } 52 }
之前创建的showRegistrationForm()方法依然还在,不过新创建的processRegistration()方法,它接受一 个Spitter对象作为参数。这个对象 有firstName、lastName、username和password属性,这些属性将会使用请求中同名的参数进行填充。
当使用Spitter对象调用processRegistration()方法时,它会进而调用SpitterRepository的save()方 法,SpitterRepository是在SpitterController的构造器中 注入进来的。
processRegistration()方法做的最后一件事就是返回一 个String类型,用来指定视图。但是这个视图格式和以前的视图有所不同。这里不仅返回了视图的名称供视图解析器查找目 标视图,而且返回的值还带有重定向的格式。 如果Spitter.username属性的值为“jbauer”,那么视图将会重 定向到“/spitter/jbauer”。
需要注意的是,除 了“redirect:”,InternalResourceViewResolver还能识 别“forward:”前缀。当它发现视图格式中以“forward:”作为前缀 时,请求将会前往(forward)指定的URL路径,而不再是重定向。
并且在processRegistration()方法中启用校验功能,Spitter参数添加了@Valid注解,这会告知 Spring,需要确保这个对象满足校验限制。 在Spitter属性上添加校验限制并不能阻止表单提交。即便用户没 有填写某个域或者某个域所给定的值超出了最大长 度,processRegistration()方法依然会被调用。这样,我们就 需要处理校验的错误,就像在processRegistration()方法中所 看到的那样。
如果有校验出现错误的话,那么这些错误可以通过Errors对象进行 访问,现在这个对象已作为processRegistration()方法的参 数。(很重要一点需要注意,Errors参数要紧跟在带有@Valid注 解的参数后面,@Valid注解所标注的就是要检验的参 数。)processRegistration()方法所做的第一件事就是调 用Errors.hasErrors()来检查是否有错误。
- 如果有错误的话,Errors.hasErrors()将会返回 到registerForm,也就是注册表单的视图。这能够让用户的浏览 器重新回到注册表单页面,所以他们能够修正错误,然后重新尝试提 交。
- 如果没有错误的话,Spitter对象将会通过Repository进行保存,控 制器会像之前那样重定向到基本信息页面。
5.Spitter类,在属性上添加校验注解
1 package spittr.spitter; 2 3 import javax.validation.constraints.NotNull; 4 import javax.validation.constraints.Size; 5 6 import org.apache.commons.lang3.builder.EqualsBuilder; 7 import org.apache.commons.lang3.builder.HashCodeBuilder; 8 9 public class Spitter { 10 11 private Long id; 12 13 @NotNull 14 @Size(min = 5, max = 16) 15 private String username; 16 17 @NotNull 18 @Size(min = 5, max = 25) 19 private String password; 20 21 @NotNull 22 @Size(min = 2, max = 30) 23 private String firstName; 24 25 @NotNull 26 @Size(min = 2, max = 30) 27 private String lastName; 28 29 public Spitter() { 30 } 31 32 public Spitter(String username, String password, String firstName, String lastName) { 33 this(null, username, password, firstName, lastName); 34 } 35 36 public Spitter(Long id, String username, String password, String firstName, String lastName) { 37 this.id = id; 38 this.username = username; 39 this.password = password; 40 this.firstName = firstName; 41 this.lastName = lastName; 42 } 43 44 public String getUsername() { 45 return username; 46 } 47 48 public void setUsername(String username) { 49 this.username = username; 50 } 51 52 public String getPassword() { 53 return password; 54 } 55 56 public void setPassword(String password) { 57 this.password = password; 58 } 59 60 public Long getId() { 61 return id; 62 } 63 64 public void setId(Long id) { 65 this.id = id; 66 } 67 68 public String getFirstName() { 69 return firstName; 70 } 71 72 public void setFirstName(String firstName) { 73 this.firstName = firstName; 74 } 75 76 public String getLastName() { 77 return lastName; 78 } 79 80 public void setLastName(String lastName) { 81 this.lastName = lastName; 82 } 83 84 @Override 85 public boolean equals(Object that) { 86 return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email"); 87 } 88 89 @Override 90 public int hashCode() { 91 return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email"); 92 } 93 94 }
Spitter的所有属性都添加了@NotNull注解,以确保它们的 值不为null。类似地,属性上也添加了@Size注解以限制它们的长 度在最大值和最小值之间。对Spittr应用来说,这意味着用户必须 要填完注册表单,并且值的长度要在给定的范围内。
Java校验API定义了多个注解,这些注解可以放到属性上,从而限制 这些属性的值。所有的注解都位于 javax.validation.constraints包中。
6.测试