⭐表示重要。

第一章:前言

  • 在 Web 应用三层架构体系中,表现层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。
  • 为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

第二章:数据校验概述(⭐)

  • JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。
  • JSR 303 通过在 Bean 属性上标注类似于 @NotNull@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。 | 注解 | 规则 | | —- | —- | | @Null | 标注值必须为 null | | @NotNull | 标注值不可为 null | | @AssertTrue | 标注值必须为 true | | @AssertFalse | 标注值必须为 false | | @Min(value) | 标注值必须大于或等于 value | | @Max(value) | 标注值必须小于或等于 value | | @DecimalMin(value) | 标注值必须大于或等于 value | | @DecimalMax(value) | 标注值必须小于或等于 value | | @Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 | | @Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 | | @Past | 标注值只能用于日期型,且必须是过去的日期 | | @Future | 标注值只能用于日期型,且必须是将来的日期 | | @Pattern(value) | 标注值必须符合指定的正则表达式 |
  • JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解: | 注解 | 规则 | | —- | —- | | @Email | 标注值必须是格式正确的 Email 地址 | | @Length | 标注值字符串大小必须在指定的范围内 | | @NotEmpty | 标注值字符串不能是空字符串 | | @Range | 标注值必须在指定的范围内 |
  • Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 <mvc:annotation-driven/> 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将 JSR 303 的实现者的 jar 包放到类路径下。
  • 配置 <mvc:annotation-driven/> 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过 在处理方法的入参上标注 @Validated 注解 ,即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。

第三章:数据校验应用(⭐)

3.1 环境准备

  • 略(在 SpringMVC 环境的基础上)。

3.2 导入依赖

  • pom.xml
  1. <dependency>
  2. <groupId>org.hibernate.validator</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>6.2.0.Final</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.hibernate.validator</groupId>
  8. <artifactId>hibernate-validator-annotation-processor</artifactId>
  9. <version>6.2.0.Final</version>
  10. </dependency>

注意:需要 Tomcat 版本至少是 8。并不是说 Tomcat 6 不能做 SpringMVC 的数据校验,而是所需依赖不同。

3.3 应用校验规则

3.3.1 实体类上应用校验规则

  • 在实体类中需要附加规则的成员变量上标记校验规则注解。

  • 示例:

  1. package com.github.fairy.era.mvc.entity;
  2. import javax.validation.constraints.Email;
  3. import javax.validation.constraints.Size;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-11-16 15:30
  8. */
  9. public class User {
  10. // 字符串的长度是[3,6]
  11. @Size(min = 3, max = 6, message = "用户名的长度不是[3,6]")
  12. private String username;
  13. private String password;
  14. @Email(message = "邮箱的格式不正确")
  15. private String email;
  16. public String getUsername() {
  17. return username;
  18. }
  19. public void setUsername(String username) {
  20. this.username = username;
  21. }
  22. public String getPassword() {
  23. return password;
  24. }
  25. public void setPassword(String password) {
  26. this.password = password;
  27. }
  28. public String getEmail() {
  29. return email;
  30. }
  31. public void setEmail(String email) {
  32. this.email = email;
  33. }
  34. @Override
  35. public String toString() {
  36. return "User{" +
  37. "username='" + username + '\'' +
  38. ", password='" + password + '\'' +
  39. ", email='" + email + '\'' +
  40. '}';
  41. }
  42. }

3.3.2 在handler 方法形参上标记 @Validated 注解

  • 示例:
  1. package com.github.fairy.era.mvc.handler;
  2. import com.github.fairy.era.mvc.entity.User;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.validation.annotation.Validated;
  7. import org.springframework.web.bind.annotation.PostMapping;
  8. /**
  9. * @author 许大仙
  10. * @version 1.0
  11. * @since 2021-11-16 15:33
  12. */
  13. @Controller
  14. public class UserHandler {
  15. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  16. @PostMapping("/save/user")
  17. public String saveUser(@Validated User user) {
  18. logger.info("UserHandler类的saveUser方法的请求参数是:{}", user);
  19. return "target";
  20. }
  21. }

3.3.3 表单

  • 示例:
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>首页</title>
  6. </head>
  7. <body>
  8. <form method="post" th:action="@{/save/user}">
  9. 用户名: <input name="username" type="text"> <br>
  10. 密码: <input name="password" type="password"> <br>
  11. 邮箱:<input name="email" type="text"> <br>
  12. <button>新增用户</button>
  13. </form>
  14. </body>
  15. </html>

3.3.4 校验失败效果

  • 日志信息:
  1. [15:40:12.639] [DEBUG] [http-nio-8080-exec-6] [org.springframework.web.servlet.DispatcherServlet] [POST "/ajax/save/user", parameters={masked}]
  2. [15:40:12.640] [WARN ] [http-nio-8080-exec-6] [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver] [Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<LF>Field error in object 'user' on field 'username': rejected value [adminadmin]; codes [Size.user.username,Size.username,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username],6,3]; default message [用户名的长度不是[3,6]]]]
  • 页面:

校验失效页面效果.png

3.3.5 显示友好的错误提示

  • 重构 handler 方法:
  1. package com.github.fairy.era.mvc.handler;
  2. import com.github.fairy.era.mvc.entity.User;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.validation.BindingResult;
  7. import org.springframework.validation.FieldError;
  8. import org.springframework.validation.annotation.Validated;
  9. import org.springframework.web.bind.annotation.PostMapping;
  10. import java.util.Optional;
  11. import java.util.stream.Collectors;
  12. /**
  13. * @author 许大仙
  14. * @version 1.0
  15. * @since 2021-11-16 15:33
  16. */
  17. @Controller
  18. public class UserHandler {
  19. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  20. @PostMapping("/save/user")
  21. public String saveUser(@Validated User user, BindingResult result) {
  22. // 如果数据绑定过程中发生了错误
  23. if (result.hasErrors()) {
  24. // 如果发生了错误,就跳转到专门显示错误信息的页面
  25. // 相关的错误信息会自动放到请求域中,当然,也可以手动获取
  26. String errorMessage = Optional.ofNullable(result.getFieldError()).map(FieldError::getDefaultMessage).stream().collect(Collectors.joining(","));
  27. logger.error("ProductHandler.saveProduct.errorMessage = {}", errorMessage);
  28. return "error";
  29. }
  30. logger.info("UserHandler类的saveUser方法的请求参数是:{}", user);
  31. return "target";
  32. }
  33. }
  • 错误信息页面:
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>错误页面</title>
  6. </head>
  7. <body>
  8. <!-- th:errors 属性:用来显示请求处理过程中发生的错误 -->
  9. <!-- th:errors 属性值:访问错误信息的表达式 -->
  10. <!-- 访问错误信息的表达式:访问请求域,需要使用 ${} 格式 -->
  11. <!-- 访问请求域使用的属性名:执行数据绑定的实体类的简单类名首字母小写 -->
  12. <!-- 具体错误信息:找到实体类之后进一步访问出问题的属性名 -->
  13. <p th:errors="${user.username}">这里显示具体错误信息</p>
  14. <p th:errors="${user.password}">这里显示具体错误信息</p>
  15. <p th:errors="${user.email}">这里显示具体错误信息</p>
  16. </body>
  17. </html>