一、概述

二、Java 校验规范 JSR-380

官网

2.1 Hibernate Validator 概述

Bean Validation 2.0 是JSR第380号标准。JSR-380 标准描述
唯一实现是 Hibernate Validator ,它是规范 JSR-380 的实现。
Jakarta Bean Validation 2.0 定义了用于实体和方法验证的元数据模型和 API。默认的元数据源是注释,它具有通过使用 XML 重写和扩展元数据的能力。API 并不绑定在特定的应用层或编程模型上。它特别不绑定到 web 或持久层,并且可用于服务器端应用程序编程以及富客户端 Swing 应用程序开发人员。
validator-application-layers2.png

2.2 示例

  1. 需要引入相关的 jar 包: ```java org.hibernate.validator hibernate-validator 6.1.5.Final

org.glassfish jakarta.el 4.0.0-RC2

  1. 2. 创建具有约束的实体对象
  2. ```java
  3. @Data
  4. public class Car {
  5. @NotNull
  6. private String manufacturer;
  7. @NotNull
  8. @Size(min = 2, max = 14)
  9. private String licensePlate;
  10. @Min(2)
  11. private int seatCount;
  12. public Car(String manufacturer, String licencePlate, int seatCount) {
  13. this.manufacturer = manufacturer;
  14. this.licensePlate = licencePlate;
  15. this.seatCount = seatCount;
  16. }
  17. }
  1. 使用 Hibernate Validator 校验 ```java public class CarTest {

    // Validator是线程安全的,可以多次重复使用 private static Validator validator;

    @BeforeClass public static void setUpValidator() {

     ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
     validator = factory.getValidator();
    

    }

    @Test public void manufacturerIsNull() {

     Car car = new Car(null, "DD-AB-123", 4);
    
     Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
    
     assertEquals(1, constraintViolations.size());
     assertEquals("不能为null", constraintViolations.iterator().next().getMessage());
    

    }

    @Test public void licensePlateTooShort() {

     Car car = new Car("Morris", "D", 4);
    
     Set<ConstraintViolation<Car>> constraintViolations =
             validator.validate(car);
    
     assertEquals(1, constraintViolations.size());
     assertEquals(
             "个数必须在2和14之间",
             constraintViolations.iterator().next().getMessage()
     );
    

    }

    @Test public void seatCountTooLow() {

     Car car = new Car("Morris", "DD-AB-123", 1);
    
     Set<ConstraintViolation<Car>> constraintViolations =
             validator.validate(car);
    
     assertEquals(1, constraintViolations.size());
     assertEquals(
             "最小不能小于2",
             constraintViolations.iterator().next().getMessage()
     );
    

    }

    // 如果对象成功验证,则返回一个空集合 @Test public void carIsValid() {

     Car car = new Car("Morris", "DD-AB-123", 2);
    
     Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
     assertEquals(0, constraintViolations.size());
    

    }

}

以上示例为官网例子,通过 `ValidatorFactory` 获取类型安全的 `Validator` 使用它来完成最终字段、属性等等校验。
<a name="sJ1Hb"></a>
## 2.3 声明 Bean约束
有四种类型的 Bean 约束

- 字段
- 属性
- 容器元素
- 类

**注意:** 约束可以应用于任何访问类型(公共、私有等)的字段。不过,不支持对静态字段的约束。
<a name="949f5256"></a>
### 2.3.1 容器元素约束
可以直接在参数化类型的类型参数上指定约束: 这些约束称为容器元素约束。<br />Hibernate Validator 验证下列标准 Java 容器上指定的容器元素约束:

- 实现 `java.util.Iterable` 接口,比如 `List、Set` 。
- 实现 `java.util.Map` 接口,支持键值对。
- `java.util.Optional, java.util.OptionalInt, java.util.OptionalDouble, java.util.OptionalLong` 
- 不同的 JavaFX 实现 `JavaFX.beans.observable. ObservableValue` 。
<a name="wPipO"></a>
### 2.3.2 自定义容器类型
必须为自定义类型注册 `ValueExtractor` ,以允许检索要验证的值。
```java
public class GearBoxValueExtractor implements ValueExtractor<GearBox<@ExtractedValue ?>> {

    @Override
    public void extractValues(GearBox<@ExtractedValue ?> originalValue, ValueExtractor.ValueReceiver receiver) {
        receiver.value( null, originalValue.getGear() );
    }
}

3.3.3 嵌套容器元素

public class Car {

    private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers =
            new HashMap<>();

    //...
}

3.3.4 约束继承

当一个类实现一个接口或扩展另一个类时,在超类型上声明的所有约束注释以与在类本身上指定的约束相同的方式应用。

3.3.5 级联验证

public class Car {

    @NotNull
    @Valid
    private Person driver;

    //...
}
public class Person {

    @NotNull
    private String name;

    //...
}

如果验证了 Car 的实例,那么引用的 Person 对象也会被验证。验证引擎保证不会发生无限循环验证。

2.4 Validator 接口

public interface Validator {
    // 验证所有约束
    <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);

    // 验证指定属性名约束
    <T> Set<ConstraintViolation<T>> validateProperty(T object,
                                                     String propertyName,
                                                     Class<?>... groups);

    // 验证属性值约束
    <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,
                                                  String propertyName,
                                                  Object value,
                                                  Class<?>... groups);

    // 返回指定类对象的约束描述符
    BeanDescriptor getConstraintsForClass(Class<?> clazz);

    // 
    <T> T unwrap(Class<T> type);

    // 
    ExecutableValidator forExecutables();
}

2.5 ConstraintViolation

public interface ConstraintViolation<T> {

    // 返回此约束违反的插值错误信息
    String getMessage();

    // 返回此约束违反的非插值错误信息
    String getMessageTemplate();

    // 被验证bean的根名称
    T getRootBean();
    Class<T> getRootBeanClass();

    // 如果是 bean 约束,则对其应用 bean 实例; 如果是属性约束,则对承载该约束的属性的 bean 实例应用
    Object getLeafBean();

    Object[] getExecutableParameters();

    Object getExecutableReturnValue();

    Path getPropertyPath();

    Object getInvalidValue();

    ConstraintDescriptor<?> getConstraintDescriptor();

    <U> U unwrap(Class<U> type);
}

2.6 内置约束

约束名称 支持数据类型 描述
@AssertFalse boolean 被注解描述的元素是否为false
@AssertTrue boolean
@DecimalMax(value=, inclusive=) 数值类型及其包装类 inclusive=false : 值 > 指定的最大值 ?
@DecimalMin(value=, inclusive=) 数值类型及其包装类 inclusive=false : 值 > 指定的最小值 ?
@Digits(integer=, fraction=) 数值类型及其包装类 integer :整数位个数(最多)
fraction : 小数位个数(最多)
@Email CharSequence 正则校验
@Future
@Max(value=)
@Min(value=)
@NotBlank CharSequence
@NotEmpty
@NotNull
@Null
@Pattern(regex=, flags=) CharSequence

2.7 创建自定义约束

2.7.1 三个步骤

  1. 创建一个约束注解
  2. 实现一个验证器
  3. 定义默认错误消息 ```java /**

    • 1 定义约束注解

      */ @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Constraint(validatedBy = CheckCaseValidator.class) @Documented @Repeatable(CheckCase.List.class) public @interface CheckCase {

      String message() default “{org.hibernate.validator.referenceguide.chapter06.CheckCase.” +

         "message}";
      

      Class<?>[] groups() default {};

      Class<? extends Payload>[] payload() default {};

      CaseMode value();

      @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { CheckCase[] value(); } }

```java
/**
 * #2 实现一个针对该注解的验证器
 */
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        System.out.println(context);
        if (value == null) {
            return true;
        }

        boolean isValid;
        String curMode;
        if (caseMode == CaseMode.UPPER) {
            isValid = value.equals(value.toUpperCase());
            curMode = "大写";
        } else {
            isValid = value.equals(value.toLowerCase());
            curMode = "小写";
        }
        // #3 定义错误默认消息
        // 这种方式是安全的参数实现。如果使用 + 连接 value参数,在EL解析时会导致验证程序非常不安全
        HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
        if (!isValid) {
            hibernateContext.addExpressionVariable("validatedValue", value)
                    .buildConstraintViolationWithTemplate("${validatedValue} 不是" + curMode)
                    .addConstraintViolation();
        }

        return isValid;
    }
}

2.8 小结

  • Hibernate Validator 还有很多功能,包括
    • 嵌套类型校验
    • 字校验
    • 构造器校验
    • 方法校验
    • 分组校验
  • 自定义约束
  • 方便与其他框架集成

    三、Spring 对 JSR-380 支持

    Spring 底层校验的实现是 java.validation.Validator 规划,具体实现是 Hibernate Validator 框架。

    3.1 XML 继承体系

    Validator.png

    3.2 Validator(Spring)

    ```java public interface Validator {

    /**

    • 前置判断: 判断指定类型是否符合校验
    • 通过实现如: return Foo.class.isAssignableFrom(clazz); */ boolean supports(Class<?> clazz);

      /**

    • 验证提供的目标对象,该对象必须是Supports(Class)方法通常具有(或会返回)true的Class。
    • 提供的错误实例可用于报告任何导致的验证错误 */ void validate(Object target, Errors errors);

}

该接口定义基本的校验方法 `validate(Object, Errors)` ,并把任何导致校验错误写入 `Errors` 对象。
<a name="Ya9Nq"></a>
## 3.3 SmartValidator
该接口在常规校验基本上增加 `校验组` 概念,也增加针对属性值进行校验方法。
```java
public interface SmartValidator extends Validator {

    // JSR-303 校验组
    void validate(Object target, Errors errors, Object... validationHints);

    // 验证为目标类型上的指定字段提供的值
    default void validateValue(
            Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {

        throw new IllegalArgumentException("Cannot validate individual value for " + targetType);
    }

}

3.4 javax.validation.Validator

JSR-380(关于校验最新规范) 规范所定义的接口。接口详情见 2.4小结

3.5 SpringValidatorAdaptor

  • Spring Validator 适配器 ,分别实现 SmartValidator(Spring)javax.validation.Validator(JSR-380) 两个接口,将Spring的校验行为适配 JSR-380 并最终由 Hibernate Validator 框架完成最终校验。
  • Hibernate Validator 校验所得结果信息(由 Set<ConstraintViolation<Object>> 封装)包装为 Spring 体系的 Error 对象。

    3.6 小结

  • Spring 底层实现还是依靠 JSR-380/JSR-303 校验规范,通过适配器 SpringValidatorAdaptor 完成适配。

  • Spring 把校验结果转换为自身错误体系 — Error 对象,用于后续操作。

    四、@Validated & @Valid

    | 功能 | @Validated | @Valid | | —- | —- | —- | | 注解所属 | org.springframework.validation.annotation | java.validation | | 是否支持分组 | YES | NO | | | | | | | | | | | | | | | | |

五、实际应用

5.1 普通方法

  • 方法所在的类添加 @Validated
  • 待校验方法参数添加 @Valid

    5.2 基本类型

  • 方法所在的类添加 @Validated@Valid 注解无效

    5.3 Controller 中的基本类型

  • 对于接口层次简单参数的校验需要借助Spring对于普通方法校验的功能,必须在类级别上添加@Valiv=dated注解。

    六、总结