Spittr应用有两个基本的领域概念:Spitter(应用的用户)和 Spittle(用户发布的简短状态更新)。
在笔记25中已经对Spittle进行了简单的构建,现在对Spitter进行实现。主要是用户的注册、用户基本信息的展示。
注册的时候就会涉及到对表单的处理,使用表单分为两个方面:展现表单以及处理用户通过表单提交的数据。在Spittr应用中,我们需要有个表单让新用户进行注册。SpitterController是一个新的控制器,目前只有一个请求处理的方法来展现注册表单。
1.首先构建数据访问的Repository。为了实现解耦以及避免 陷入数据库访问的细节之中,我们将Repository定义为一个接口,并在稍后实现它我们只需要一个能够获取Spitter对象的Repository,如下所示的SpitterRepositorys.java
1 package myspittr.data; 2 3 import myspittr.spitter.Spitter; 4 5 public interface SpitterRepositorys { 6 Spitter save(Spitter spitter); 7 8 Spitter findByUsername(String username); 9 }
2.然后创建它的实现类JdbcSpitterRepository2.java用来访问数据库,然后读取数据,但是目前还不需要对数据库进行操作,所以需要自己做一下假数据。
1 package myspittr.data; 2 3 import org.springframework.stereotype.Service; 4 5 import myspittr.spitter.Spitter; 6 7 @Service 8 public class JdbcSpitterRepository2 implements SpitterRepositorys { 9 10 private Spitter savedSpitter; 11 12 public JdbcSpitterRepository2() { 13 } 14 15 public Spitter save(Spitter spitter) { 16 // TODO Auto-generated method stub 17 Spitter spitter2 = new Spitter(spitter.getUsername(), spitter.getPassword(), spitter.getFirstName(), 18 spitter.getLastName()); 19 this.savedSpitter = spitter2; 20 return spitter2; 21 } 22 23 public Spitter findByUsername(String username) { 24 // TODO Auto-generated method stub 25 if (username.equals(savedSpitter.getUsername())) { 26 return savedSpitter; 27 } else { 28 return null; 29 } 30 31 } 32 33 }
3.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来渲染注册表单。
4.测试展现表单的控制器方法
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。
5.渲染注册表单的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="firstName"/><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”上。
6.在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进行保存,控 制器会像之前那样重定向到基本信息页面。
7.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包中。
8.基本信息展示,profile.jsp 用来展示用户的username和firstName
1 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 <%@ page session="false" %> 3 <html> 4 <head> 5 <title>Spitter</title> 6 <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />" > 7 </head> 8 <body> 9 <h1>Your Profile</h1> 10 <c:out value="${spitter.username}" /><br/> 11 <c:out value="${spitter.firstName}" /> <c:out value="${spitter.lastName}" /><br/> 12 </body> 13 </html>
9.测试
部署到服务器上会发生以下错误,即无法创建JdbcSpitterRepository2这个bean。
需要在JdbcSpitterRepository2和JdbcSpittleRepository2两个类前增加一个注解,@Service.
@Service用于标注业务层组件,@Controller用于标注控制层组件
主页:
点击Spittles,展示最近发布的20个spittle
返回点击Register:
填入个人信息,点击注册:
使用注解的方式进行表单校验时发生错误,一直未解决,现在给出另一种校验方式
1.spring中自带框架校验器
(1)spring 校验器接口
(2)spring 提供的校验类工具,可以提供相应的校验
2.代码示例
<1>Spitter.java 没有任何注解
1 package myspittr.spitter; 2 3 import org.apache.commons.lang3.builder.EqualsBuilder; 4 import org.apache.commons.lang3.builder.HashCodeBuilder; 5 6 public class Spitter { 7 8 private Long id; 9 10 private String username; 11 12 private String password; 13 14 private String firstName; 15 16 private String lastName; 17 18 public Spitter() { 19 } 20 21 public Spitter(String username, String password, String firstName, String lastName) { 22 this(null, username, password, firstName, lastName); 23 } 24 25 public Spitter(Long id, String username, String password, String firstName, String lastName) { 26 this.id = id; 27 this.username = username; 28 this.password = password; 29 this.firstName = firstName; 30 this.lastName = lastName; 31 } 32 33 public String getUsername() { 34 return username; 35 } 36 37 public void setUsername(String username) { 38 this.username = username; 39 } 40 41 public String getPassword() { 42 return password; 43 } 44 45 public void setPassword(String password) { 46 this.password = password; 47 } 48 49 public Long getId() { 50 return id; 51 } 52 53 public void setId(Long id) { 54 this.id = id; 55 } 56 57 public String getFirstName() { 58 return firstName; 59 } 60 61 public void setFirstName(String firstName) { 62 this.firstName = firstName; 63 } 64 65 public String getLastName() { 66 return lastName; 67 } 68 69 public void setLastName(String lastName) { 70 this.lastName = lastName; 71 } 72 73 @Override 74 public boolean equals(Object that) { 75 return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email"); 76 } 77 78 @Override 79 public int hashCode() { 80 return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email"); 81 } 82 83 }
<2>自实现校验器SpitterValidator.java类
rejectIfEmpty函数由三个参数,第一个是返回错误,第二个是绑定的属性名,第三个是返回的错误信息(使用资源文件)。
1 package myspittr.validates; 2 3 import org.springframework.validation.Errors; 4 import org.springframework.validation.ValidationUtils; 5 import org.springframework.validation.Validator; 6 7 import myspittr.spitter.Spitter; 8 9 public class SpitterValidator implements Validator { 10 11 @Override 12 public boolean supports(Class<?> arg0) { 13 // TODO Auto-generated method stub 14 return false; 15 } 16 17 @Override 18 public void validate(Object arg0, Errors errors) { 19 // TODO Auto-generated method stub 20 Spitter spitter = (Spitter) arg0; 21 22 // 非空校验 23 ValidationUtils.rejectIfEmpty(errors, "firstName", "spittr.firstName"); 24 ValidationUtils.rejectIfEmpty(errors, "lastName", "spittr.lastName"); 25 ValidationUtils.rejectIfEmpty(errors, "username", "spittr.username"); 26 ValidationUtils.rejectIfEmpty(errors, "password", "spittr.password"); 27 } 28 29 }
<3>调用 重写SpitterController.java中的processRegistration方法。
1 @RequestMapping(value = "/register", method = RequestMethod.POST) 2 public String processRegistration(@Validated Spitter spitter, BindingResult errors) { 3 4 SpitterValidator spitterValidator = new SpitterValidator(); 5 spitterValidator.validate(spitter, errors); 6 if (errors.hasErrors()) { 7 return "registerForm2"; // 如果校验出现错误,则重新返回表单 8 } else { 9 spitterRepository.save(spitter); // 保存Spitter 10 return "redirect:/spitter/" + spitter.getUsername(); // 重定向到基本信息页 11 } 12 13 }
<4>资源文件
<5>registerForm.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 <%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf"%> 3 <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 4 5 <% 6 String path = request.getContextPath(); 7 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 8 %> 9 10 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 11 <html> 12 <head> 13 <base href="<%=basePath%>"> 14 15 <title>注册</title> 16 17 <meta http-equiv="pragma" content="no-cache"> 18 <meta http-equiv="cache-control" content="no-cache"> 19 <meta http-equiv="expires" content="0"> 20 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 21 <meta http-equiv="description" content="This is my page"> 22 <!-- 23 <link rel="stylesheet" type="text/css" href="styles.css"> 24 --> 25 26 </head> 27 28 <body> 29 <h1>Register</h1> 30 <sf:form method="POST" modelAttribute="spitter"> 31 First Name:<sf:input path="firstName" /> 32 <br> 33 <sf:errors path="firstName"> 34 35 </sf:errors> 36 <br> 37 38 Last Name:<sf:input path="lastName" /> 39 <br> 40 <sf:errors path="lastName"> 41 42 </sf:errors> 43 <br> 44 UserName:<sf:input path="username" /> 45 <br> 46 <sf:errors path="username"> 47 48 </sf:errors> 49 <br> 50 Password:<sf:password path="password" /> 51 <br> 52 <sf:errors path="password"> 53 54 </sf:errors> 55 <br> 56 <input type="submit" value="注册"> 57 </sf:form> 58 </body> 59 </html>
<6>测试
表单为空时点击注册按钮:
某几个字段为空时: