笔记28 接受请求的输入 ——处理表单

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>测试

表单为空时点击注册按钮:

    

某几个字段为空时:

 

 

 

 

 

 

 

 

 

posted @ 2018-04-26 16:50  雨落忧伤-  阅读(440)  评论(1编辑  收藏  举报