Bean Validation

Bean Validation 只是规范,不提供具体的实现。
2009 年发布了 Bean Validation1.0 (JSR 303)规范,随后历经 Bean Validation 1.1 (JSR 349) ,Bean Validation 2.0 (JSR 380) , Jakarta Bean Validation 2.0

image.png

Spring Validation

源码地址 https://github.com/spring-projects/spring-framework/tree/main/spring-context/src/main/java/org/springframework/validation
maven依赖仓库
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-validation</artifactId>
  4. </dependency>



校验注解

判空

@NotBlank 只能用于字符串不为 null ,并且字符串 #trim() 以后 length 要大于 0
@NotEmpty 集合对象的元素不为 0 ,即集合不为空,也可以用于字符串不为 null 。
@NotNull 不能为 null 。
@Null 必须为 null
  • 数值检查
    • @DecimalMax(value) :被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
    • @DecimalMin(value) :被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
    • @Digits(integer, fraction) :被注释的元素必须是一个数字,其值必须在可接受的范围内。
    • @Positive :判断正数。
    • @PositiveOrZero :判断正数或 0 。
    • @Max(value) :该字段的值只能小于或等于该值。
    • @Min(value) :该字段的值只能大于或等于该值。
    • @Negative :判断负数。
    • @NegativeOrZero :判断负数或 0 。
  • Boolean 值检查
    • @AssertFalse :被注释的元素必须为 true 。
    • @AssertTrue :被注释的元素必须为 false 。
  • 长度检查
    • @Size(max, min) :检查该字段的 size 是否在 min 和 max 之间,可以是字符串、数组、集合、Map 等。
  • 日期检查
    • @Future :被注释的元素必须是一个将来的日期。
    • @FutureOrPresent :判断日期是否是将来或现在日期。
    • @Past :检查该字段的日期是在过去。
    • @PastOrPresent :判断日期是否是过去或现在日期。
  • 其它检查
    • @Email :被注释的元素必须是电子邮箱地址。
    • @Pattern(value) :被注释的元素必须符合指定的正则表达式。 ```java 注解 @Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
  1. eg
  2. ```java
  3. import javax.validation.constraints.Email;
  4. import javax.validation.constraints.Min;
  5. import javax.validation.constraints.NotNull;
  6. import javax.validation.constraints.Size;
  7. public class User {
  8. private Long id;
  9. @NotNull(message = "用户账号不能为空")
  10. @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
  11. private String username;
  12. @NotNull(message = "用户密码不能为空")
  13. @Size(min = 6, max = 8, message = "密码长度必须是6-8个字符")
  14. private String password;
  15. @NotNull(message = "用户邮箱不能为空")
  16. @Email(message = "邮箱格式不正确")
  17. private String email;
  18. // 不允许为空,并且年龄的最小值为18
  19. @NotNull
  20. @Min(18)
  21. private Integer age;
  22. }

Hibernate Validator

github :https://github.com/hibernate/hibernate-validator

  1. @NotBlank(message =) 验证字符串非null,且长度必须大于0
  2. @Email 被注释的元素必须是电子邮箱地址
  3. @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
  4. @NotEmpty 被注释的字符串的必须非空
  5. @Range(min=,max=,message=) 被注释的元素必须在合适的范围内

@Validated @Valid

  • @Valid:javax.validation 提供,作用在方法,构造方法,参数,成员属性上。
  • @Valid 可做嵌套校验,作用在属性上,属性是一个 Java Bean,需要对属性 Bean 的内部属性进行校验。

    1. @PostMapping("/validation")
    2. public String addUser(@RequestBody @Valid User user, BindingResult bindingResult) {
    3. // 如果有参数校验失败,返回错误信息
    4. if (bindingResult.hasErrors()) {
    5. System.out.println(user.toString());
    6. System.out.println(bindingResult.getErrorCount());
    7. System.out.println(bindingResult.getAllErrors());
    8. }
    9. for (ObjectError error : bindingResult.getAllErrors()) {
    10. return error.getDefaultMessage();
    11. }
    12. return user.toString();
    13. }

    总的来说,绝大多数场景下,我们使用 @Validated 注解即可。
    而在有嵌套校验的场景,我们使用 @Valid 注解添加到成员属性上。

    自定义约束

    自定义定义约束主要包括以下2个步骤:
    1)编写自定义约束的注解
    2)编写自定义的校验器 ConstraintValidator
    因此自定义约束由两部分组成:校验注解和校验器

    校验注解

  • 校验注解:就一个普通的 Java 注解,但多个了元注解 @Constraint 用于指定该注解的校验器。validatedBy 的值指定为校验逻辑的实现类,即校验器。

    1. @Constraint(validatedBy = {xxxxx.class})

@Retention :用来说明该注解类的生命周期。它有以下三个参数:
RetentionPolicy.SOURCE : 注解只保留在源文件中
RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
RetentionPolicy.RUNTIME : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

@Target : 用来说明该注解可以被声明在那些元素之前。
ElementType.TYPE:说明该注解只能被声明在一个类前。
ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
ElementType.PACKAGE:说明该注解只能声明在一个包名前。

@Constraint来限定自定义注解的方法

  1. @Documented
  2. @Constraint(validatedBy = OrderNumberValidator.class)
  3. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
  4. @Retention(RUNTIME)
  5. public @interface OrderNumber {
  6. String message() default "{com.acme.constraint.OrderNumber.message}";
  7. Class<?>[] groups() default {};
  8. Class<? extends Payload>[] payload() default {};
  9. }
  • 自定义校验注解必须包含 message,groups,payload 属性。
    1. String message() default [...];
    2. Class<?>[] groups() default {};
    3. Class<? extends Payload>[] payload() default {};

    eg: ```java import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType;

import javax.validation.Constraint; import javax.validation.Payload;

import cn.netkiller.web.annotation.impl.MobileValidator;

@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MobileValidator.class) @Documented // 注解的实现类。 public @interface Mobile { // 校验错误的默认信息 String message() default “手机号码格式不正确!”;

  1. // 是否强制校验
  2. boolean isRequired() default true;
  3. Class<?>[] groups() default {};
  4. Class<? extends Payload>[] payload() default {};

}

  1. <a name="oLSXd"></a>
  2. ### **校验器**
  3. - **校验器**:对校验注解进行解析,实现 ConstraintValidator接口,接口使用了泛型,需要指定两个参数,第一个是**自定义注解类**,第二个是需要校验的**数据类型**,重写 isValid(T value, ConstraintValidatorContext context)方法。
  4. ```java
  5. public interface ConstraintValidator<A extends Annotation, T> {
  6. /**
  7. * 在调用 isValid 方法之前初始化
  8. * 该方法的参数类型为自定义注解,可调用注解的方法获取注解信息
  9. * 重写该方法, 给自定义校验器中定义的类变量赋值
  10. */
  11. default void initialize(A constraintAnnotation) {
  12. }
  13. /**
  14. * 重写该方法,实现校验逻辑,返回 布尔值
  15. */
  16. boolean isValid(T value, ConstraintValidatorContext context);
  17. }

eg:

  1. import java.util.regex.Matcher;
  2. import java.util.regex.Pattern;
  3. import javax.validation.ConstraintValidator;
  4. import javax.validation.ConstraintValidatorContext;
  5. import org.springframework.util.StringUtils;
  6. import cn.netkiller.web.annotation.Mobile;
  7. public class MobileValidator implements ConstraintValidator<Mobile, String> {
  8. public MobileValidator() {
  9. // TODO Auto-generated constructor stub
  10. }
  11. private boolean required = false;
  12. @Override
  13. public void initialize(Mobile constraintAnnotation) {
  14. required = constraintAnnotation.isRequired();
  15. }
  16. @Override
  17. public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) {
  18. Pattern mobile_pattern = Pattern.compile("1\\d{10}");
  19. // System.out.println(phone);
  20. // 是否为手机号的实现
  21. if (required) {
  22. if (StringUtils.isEmpty(phone)) {
  23. return false;
  24. }
  25. Matcher m = mobile_pattern.matcher(phone);
  26. return m.matches();
  27. } else {
  28. return StringUtils.isEmpty(phone);
  29. }
  30. }
  31. }

使用

  1. public class User {
  2. // 这里是新添加的注解
  3. @Mobile(message = "手机号码格式错误")
  4. private String phone;
  5. }

参考

https://beanvalidation.org/
http://www.gxitsky.com/article/1605537736100208
https://lfvepclr.gitbooks.io/spring-framework-5-doc-cn/content/5/5-8.html
http://lazycece.com/2019/02/16/springboot%E4%B8%AD%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C%EF%BC%88validation%EF%BC%89%E6%B3%A8%E8%A7%A3%E8%87%AA%E5%AE%9A%E4%B9%89/