> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/di-5-zhang-gou-jian-spring-web-ying-yong-cheng-xu/untitled-3/untitled.md).

# 5.4.2　校验表单

如果用户在提交表单的时候，username 或 password 文本域为空的话，那么将会导致在新建 Spitter 对象中，username 或 password 是空的 String。至少这是一种怪异的行为。如果这种现象不处理的话，这将会出现安全问题，因为不管是谁只要提交一个空的表单就能登录应用。

同时，我们还应该阻止用户提交空的 firstName 和 / 或 lastName，使应用仅在一定程度上保持匿名性。有个好的办法就是限制这些输入域值的长度，保持它们的值在一个合理的长度范围，避免这些输入域的误用。

有种处理校验的方式非常初级，那就是在 processRegistration() 方法中添加代码来检查值的合法性，如果值不合法的话，就将注册表单重新显示给用户。这是一个很简短的方法，因此，添加一些额外的 if 语句也不是什么大问题，对吧？

与其让校验逻辑弄乱我们的处理器方法，还不如使用 Spring 对 Java 校验 API（Java Validation API，又称 JSR-303）的支持。从 Spring 3.0 开 始，在 Spring MVC 中提供了对 Java 校验 API 的支持。在 Spring MVC 中要使用 Java 校验 API 的话，并不需要什么额外的配置。只要保证在类路径下包含这个 Java API 的实现即可，比如 Hibernate Validator。

Java 校验 API 定义了多个注解，这些注解可以放到属性上，从而限制这些属性的值。所有的注解都位于 javax.validation.constraints 包中。表 5.1 列出了这些校验注解。

| 注解           | 描述                                             |
| ------------ | ---------------------------------------------- |
| @AssertFalse | 所注解的元素必须是 Boolean 类型，并且值为 false                |
| @AssertTrue  | 所注解的元素必须是 Boolean 类型，并且值为 true                 |
| @DecimalMax  | 所注解的元素必须是数字，并且它的值要小于或等于给定的  BigDecimalString 值 |
| @DecimalMin  | 所注解的元素必须是数字，并且它的值要大于或等于给定的  BigDecimalString 值 |
| @Digits      | 所注解的元素必须是数字，并且它的值必须有指定的位数                      |
| @Future      | 所注解的元素的值必须是一个将来的日期                             |
| @Max         | 所注解的元素必须是数字，并且它的值要小于或等于给定的值                    |
| @Min         | 所注解的元素必须是数字，并且它的值要大于或等于给定的值                    |
| @NotNull     | 所注解元素的值必须不能为 null                              |
| @Null        | 所注解元素的值必须为 null                                |
| @Past        | 所注解的元素的值必须是一个已过去的日期                            |
| @Pattern     | 所注解的元素的值必须匹配给定的正则表达式                           |
| @Size        | 所注解的元素的值必须是 String、集合或数组，并且它的长度要符合给定的范围        |

除了表 5.1中的注解，Java 校验 API 的实现可能还会提供额外的校验注解。同时，也可以定义自己的限制条件。但就我们来讲，将会关注于上表中的两个核心限制条件。

请考虑要添加到 Spitter 域上的限制条件，似乎需要使用 @NotNull 和 @Size 注解。我们所要做的事情就是将这些注解添加到 Spitter 的属性上。如下的程序清单展现了 Spitter 类，它的属性已经添加了校验注解。

```java
package spittr;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Spitter {

  private Long id;
  
  @NotNull
  @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;
  
  ...
}
```

现在，Spitter 的所有属性都添加了 @NotNull 注解，以确保它们的值不为 null。类似地，属性上也添加了 @Size 注解以限制它们的长度在最大值和最小值之间。对 Spittr 应用来说，这意味着用户必须要填完注册表单，并且值的长度要在给定的范围内。

我们已经为 Spitter 添加了校验注解，接下来需要修改 processRegistration() 方法来应用校验功能。启用校验功能的 processRegistration() 如下所示：

{% code title="程序清单 5.19　processRegistration()：确保所提交的数据是合法的" %}

```java
@RequestMapping(value="/register", method=POST)
public String processRegistration(
    @Valid Spitter spitter, 
    Errors errors) {
  if (errors.hasErrors()) {
    return "registerForm";
  }
    
  spitterRepository.save(spitter);
  return "redirect:/spitter/" + spitter.getUsername();
}
```

{% endcode %}

与程序清单5.17 中最初的 processRegistration() 方法相比，这里有了很大的变化。Spitter 参数添加了 @Valid 注解，这会告知 Spring，需要确保这个对象满足校验限制。

在 Spitter 属性上添加校验限制并不能阻止表单提交。即便用户没有填写某个域或者某个域所给定的值超出了最大长度，processRegistration() 方法依然会被调用。这样，我们就需要处理校验的错误，就像在 processRegistration() 方法中所看到的那样。

如果有校验出现错误的话，那么这些错误可以通过 Errors 对象进行访问，现在这个对象已作为 processRegistration() 方法的参数。（很重要一点需要注意，Errors 参数要紧跟在带有 @Valid 注解的参数后面，@Valid 注解所标注的就是要检验的参数。）processRegistration() 方法所做的第一件事就是调用Errors.hasErrors() 来检查是否有错误。

如果有错误的话，Errors.hasErrors() 将会返回到registerForm，也就是注册表单的视图。这能够让用户的浏览器重新回到注册表单页面，所以他们能够修正错误，然后重新尝试提交。现在，会显示空的表单，但是在下一章中，我们将在表单中显示最初提交的值并将校验错误反馈给用户。

如果没有错误的话，Spitter 对象将会通过 Repository 进行保存，控制器会像之前那样重定向到基本信息页面。&#x20;
