Jakarta Bean Validation 漫谈

Bean Validation 漫谈

1.Bean Validation的前世今生

Jakarta Bean Validation 规范最早在 Oracle Java EE 下维护。2017 年 11 月,Oracle 将 Java EE 移交给 Eclipse 基金会。 2018 年 3 月 5 日,Eclipse 基金会宣布 Java EE (Enterprise Edition) 被更名为 Jakarta EE。随着JSR-303JSR-349JSR-380提案的相继问世(分别对应Bean Validation 1.0、Bean Validation 1.1和Bean Validation 2.0)。

2020年7月发布的 Jakarta Bean Validation 3.0 在 Jakarta Bean Validation 2.0 的基础上,彻底将包命名空间迁移到 jakarta.validation,而不再是 javax.validation。当前版本为Jakarta Bean Validation 3.1,Maven坐标为:

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>3.1.0</version>
</dependency>

2.Bean Validation的介绍

Bean Validation 是一套Java的规范,它可以

  1. 通过使用注解的方式在对象模型上表达约束;
  2. 以扩展的方式编写自定义约束;
  3. 提供了用于验证对象和对象图的API;
  4. 提供了用于验证方法和构造方法的参数和返回值的API;
  5. 报告违反约定的集合;

在 Jakarta Bean Validation 规范中,有一些核心 API 如下:

  • Validator,用于校验常规 Java Bean,同时支持分组校验;
  • ExecutableValidator,用于校验方法参数与方法返回值,同样支持分组校验。方法参数和方法返回值往往并不是一个常规 Java Bean,可能是一种容器,比如:List、Map 和 Optional 等;Java 8 针对ElementType新增了一个 TYPE_USE 枚举实例,这让容器元素的校验变得简单,Jakarta Bean Validation API 中内置的注解式约束的头上均有 TYPE_USE 的身影。
  • ConstraintValidator,如果 Jakarta Bean Validation API 中内置的注解式约束不能满足实际的需求,则需要自定义注解式约束,同时还需要为自定义约束指定校验器,这个校验器需要实现 ConstraintValidator 接口。
  • ValueExtractor,容器并不仅仅指的是 JDK 类库中的 List、Map 和 Set 等,也可以是一些包装类,比如ResponseEntity;如果要想校验 ResponseEntity 容器中的 body,那么就需要通过实现 ValueExtractor 接口来自定义一个容器元素抽取器,然后通过ConfigurationaddValueExtractor()方法注册自定义 ValueExtractor。

其内置 Jakarta Bean Validation 3.0的内置约束有:

Constraint 描述 支持类型
@Null 被注释的元素必须为 null 任意类型
@NotNull 被注释的元素必须不为 null 任意类型
@AssertTrue 被注释的元素必须为 true booleanBoolean
@AssertFalse 被注释的元素必须为 false booleanBoolean
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 BigDecimalBigIntegerbyteshortintlong 以及各自的包装类
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 BigDecimalBigIntegerbyteshortintlong 以及各自的包装类
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 BigDecimalBigIntegerCharSequencebyteshortintlong 以及各自的包装类
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 BigDecimalBigIntegerCharSequencebyteshortintlong 以及各自的包装类
@Size(max, min) 被注释的元素的大小必须在指定的范围内 CharSequenceCollectionMapArray
@Negative 被标注元素必须为是一个严格意义上的负数(即0被认为是无效的) BigDecimalBigIntegerbyteshortintlongfloatdouble以及各自的包装类
@NegativeOrZero 被标注元素必须为是负数或者0 BigDecimalBigIntegerbyteshortintlongfloatdouble以及各自的包装类
@Positive 被标注元素必须为是一个严格意义上的正数(即0被认为是无效的) BigDecimalBigIntegerbyteshortintlongfloatdouble以及各自的包装类
@Positive OrZero 被标注元素必须为是正数或者0 BigDecimalBigIntegerbyteshortintlongfloatdouble以及各自的包装类
@Digits 被标注元素必须是在可接受范围内的数字 BigDecimalBigIntegerCharSequencebyteshortintlong 以及各自的包装类
@Pattern 被标注的CharSequence必须匹配指定的正则表达式,该正则表达式遵循Java的正则表达式规定 CharSequence
@NotEmpty 被标注元素必须不为null或者空 CharSequenceCollectionMapArray
@NotBlank 被标注元素必须不为null,并且必须包含至少一个非空格的字符 CharSequence
@Email 字符串必须是符合正确格式的电子邮件地址 CharSequence

详见: Jakarta Bean Validation 3.0

Jakarta Bean Validation 规范的唯一实现为 Hibernate Validator(官网:Hibernate Validator),会附加一些第三方的的约束,详见:2.3.2. Additional constraints

3.Bean Validation的违约处理

上述注解作用于字段或方法上时,对于违反约定的请求参数,Bean Validation会抛出异常:MethodArgumentNotValidException

image-20240528104058574 image-20240528104229638

重点看一下AbstractBindingResultBindingResult的抽象类

image-20240528104500920

其中objectName是校验Bean的名称,errors就是抛出的异常,进一步看ObjectError的实现类FieldError

image-20240528105035668

field是违约的字段,rejectedValue是该字段显式定义的拒绝的值,bindingFailure表示是否为绑定失败,如果不是的话则说明是校验失败,进一步跟进ObjectError可以看到:

image-20240528110829904

其中objectName为绑定校验的对象名,source为违反约束的实例对象,继续看其父类消息处理类:

image-20240528111232635

其中arguments是处理消息的参数,defaultMessage则是默认消息,会被赋予注解中的message字段值。

4.Bean Validation的使用

参考:基础设施建设——全局参数校验

5.Spring Validator 与 Bean Validation的关系

5.1 Spring的Validator接口

在Spring Framework中有自己的Validator接口,但是其API 设计的比较简陋,而且需要编写大量 Validator 实现类,与javax bean validation的注解式校验相比略显复杂,于是在Spring 3.0版本开始,Spring Validator将其所有校验请求转发至Jakarta Bean Validation接口的实现中。

image-20240528151130219

其中一个非常重要的类就是SpringValidatorAdapter,不仅实现了 SmartValidator 接口,同时也实现了jakarta.validation.Validator接口。(SmartValidator接口继承自spring的Validator接口)顾名思义,xxxAdapter就是适配层,所以SpringValidatorAdapter的核心作用就是将校验请求转发给jakarta.validation.Validator

public class SpringValidatorAdapter implements SmartValidator, Validator {
    @Nullable
    private Validator targetValidator;

    public void validate(Object target, Errors errors) {
        if (this.targetValidator != null) {
            this.processConstraintViolations(this.targetValidator.validate(target, new Class[0]), errors);
        }
    }

    public void validate(Object target, Errors errors, Object... validationHints) {
        if (this.targetValidator != null) {
            this.processConstraintViolations(this.targetValidator.validate(target, this.asValidationGroups(validationHints)), errors);
        }
    }
}
//---------------------------------------------------------------------
// Implementation of JSR-303 Validator interface
//---------------------------------------------------------------------
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
    Assert.state(this.targetValidator != null, "No target Validator set");
    return this.targetValidator.validate(object, groups);
}

5.2 Spring MVC 是如何进行 Bean 校验的

LocalValidatorFactoryBean 继承自 SpringValidatorAdapter,负责构建与配置 jakarta.validation.Validator 实例,并且实现了InitializingBean接口,在后者 afterPropertiesSet() 方法内进行构建与配置 jakarta.validation.Validator 实例,然后通过 setTargetValidator() 方法为 SpringValidatorAdapter 注入 Bean Validation 实例。

image-20240528161416816

在 Spring MVC 中,HandlerMethodArgumentResolver一般会委派HttpMessageConverter从 HTTP 请求中解析出HandlerMethod所需要的方法参数值 (有了参数才能反射调用由@RestController注解标记的方法),然后进行 Bean Validation 操作。RequestResponseBodyMethodProcessor是极为重要的一个 HandlerMethodArgumentResolver 实现类,因为由@RequestBody标记的参数就由它解析。

image-20240528155809865

validateIfApplicable方法首先通过determineValidationHints方法判断注解是@Valid还是@Validated,然后调用dataBinder.validate方法校验参数。

image-20240528160057907 image-20240528161631280

最终还是通过SpringValidatorAdapter发送到 jakarta.validation.Validator<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups)方法,(BindingResult继承自Errors)

image-20240528161838294

参考文献:

[1] Spring:全面拥抱 Jakarta Bean Validation 规范.https://cloud.tencent.com/developer/article/2322140


本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。

posted @ 2024-05-28 16:31  爱吃麦辣鸡翅  阅读(202)  评论(0编辑  收藏  举报