深入了解数据校验(Bean Validation):从深处去掌握@Valid的作用(级联校验)以及常用约束注解的解释说明【享学Java】(中)

简介: 深入了解数据校验(Bean Validation):从深处去掌握@Valid的作用(级联校验)以及常用约束注解的解释说明【享学Java】(中)

总得来说,我个人建议不能光只记忆结论,因为那很容易忘记,所以还是得稍微深入一点,让记忆更深刻吧。那就从下面四个方面深入:


检索Field:getFieldMetaData( beanClass )


  1. 拿到本类所有字段Field:clazz.getDeclaredFields()
  2. 把每个Field都包装成ConstrainedElement存放起来~~~1. 注意:此步骤完成了对每个Field上标注的注解进行了保存


检索Method:getMethodMetaData( beanClass )

  1. 拿到本类所有的方法Method:clazz.getDeclaredMethods()
  2. 排除掉静态方法和合成(isSynthetic)方法
  3. 把每个Method都转换成一个ConstrainedExecutable装着~~(ConstrainedExecutable也是个ConstrainedElement)。在此期间它完成了如下事(方法和构造器都复杂点,因为包含入参和返回值):1. 找到方法上所有的注解保存起来2. 处理入参、返回值(包括自动判断是作用在入参还是返回值上)


检索Constructor:getConstructorMetaData( beanClass )


完全同处理Method,略


检索Type:getClassLevelConstraints( beanClass )


  1. 找打标注在此类上的所有的注解,转换成ConstraintDescriptor
  2. 对已经找到每个ConstraintDescriptor进行处理,最终都转换Set<MetaConstraint<?>>这个类型
  3. 把Set<MetaConstraint<?>>用一个ConstrainedType包装起来(ConstrainedType是个ConstrainedElement)


关于级联校验此处补充说明一点,处理Type,都会处理级联校验情况,并且还是递归处理:

也就是这个方法(课件@Valid在此处生效):

  // type解释:分如下N中情况
  // Field为:.getGenericType() // 字段的类型
  // Method为:.getGenericReturnType() // 返回值类型
  // Constructor:.getDeclaringClass() // 构造器所在类
  // annotatedElement:可不一定说一定要有注解才能进来(每个字段、方法、构造器等都能传进来)
  private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElement annotatedElement, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) {
    return CascadingMetaDataBuilder.annotatedObject( type, annotatedElement.isAnnotationPresent( Valid.class ), containerElementTypesCascadingMetaData, getGroupConversions( annotatedElement ) );
  }


这里对我们理解级联校验最重要的一句是:annotatedElement.isAnnotationPresent(Valid.class)。也就是说:若元素被此注解标注了,那就证明需要对它进行级联校验,这就是JSR定位@Valid的作用~


Spring提升了它???请关注后文Spring对它的应用吧~


ConstraintValidator.isValid()调用处


我们知道,每个约束注解都是交给约束校验器ConstraintValidator.isValid()这个方法来处理的,它被调用(生效)的地方在此(唯一处):


public abstract class ConstraintTree<A extends Annotation> {
  ...
  protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
      ValueContext<?, ?> valueContext,
      ConstraintValidatorContextImpl constraintValidatorContext,
      ConstraintValidator<A, V> validator) {
    ...
    V validatedValue = (V) valueContext.getCurrentValidatedValue();
    isValid = validator.isValid( validatedValue, constraintValidatorContext );
    ...
    // 显然校验不通过就返回错误消息  否则返回空集合
    if ( !isValid ) {
      return executionContext.createConstraintViolations(valueContext, constraintValidatorContext);
    }
    return Collections.emptySet();
  }
  ...
}

这个方法的调用,会在执行每个Group的时候

success = metaConstraint.validateConstraint( validationContext, valueContext );



MetaConstraint在上面检索的时候就已经准备好了,最后通过ConstrainedElement.getConstraints就拿到了每个元素的校验器们,继续调用


// ConstraintTree<A>
boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext );


so,最终就调用到了isValid这个真正做事的方法上了。


说了这么多,你可能还云里雾里,那么就show一把吧:


Demo Show


上面用一个示例校验Person这个JavaBean了,但是你会发现示例中我们全都是校验的Field属性。从理论里我们知道了Bean Validation它是有校验方法、构造器、入参甚至递归校验级联属性的能力的:


校验属性Field



校验Method入参、返回值


校验Constructor入参、返回值


既校验入参,同时也校验返回值


这些是不能直接使用的,需要在运行时进行校验。具体使用可参考:【小家Spring】让Controller支持对平铺参数执行数据校验(默认Spring MVC使用@Valid只能对JavaBean进行校验)


级联校验

什么叫级联校验,其实就是带校验的成员里存在级联对象时,也要对它完成校验。这个在实际应用场景中是比较常见的,比如入参Person对象中,还持有Child对象,我们不仅仅要完成Person的校验,也依旧还要对Child内的属性校验:


@Getter
@Setter
@ToString
public class Person {
    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;
    @Valid
    @NotNull
    private InnerChild child;
    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }
}


校验逻辑如下:


    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fsx");
        Person.InnerChild child = new Person.InnerChild();
        child.setName("fsx-son");
        child.setAge(-1);
        person.setChild(child); // 放进去
        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);
        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }


运行:

child.age 必须是正数: -1
age 不能为null: null

child.age这个级联属性校验成功~

相关文章
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
312 7
|
Java 编译器 开发者
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
345 25
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
420 43
Java学习十六—掌握注解:让编程更简单
|
SQL Java 数据库连接
如何用 Java 校验 SQL 语句的合法性?
本文介绍了五种校验 SQL 语句合法性的方案:1) 使用 JDBC API 的 `execute()` 方法,通过捕获异常判断合法性;2) 使用 JSqlParser 库解析 SQL 语句为 Java 对象;3) 使用正则表达式检查 SQL 语句格式;4) 使用 ANTLR 生成 SQL 解析器;5) 使用 Apache Calcite 解析 SQL。每种方法各有优劣,具体选择取决于需求和个人偏好。需要注意的是,这些方法仅能校验语法合法性,无法保证语义正确性,仍需防范 SQL 注入攻击。
855 6
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
605 5
|
Arthas Java 测试技术
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
Java字节码文件、组成、详解、分析;常用工具,jclasslib插件、阿里arthas工具;如何定位线上问题;Java注解
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
546 0
java 常用注解大全、注解笔记
|
Java 编译器 程序员
Java注解,元注解,自定义注解的使用
本文讲解了Java中注解的概念和作用,包括基本注解的用法(@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface),Java提供的元注解(@Retention, @Target, @Documented, @Inherited),以及如何自定义注解并通过反射获取注解信息。
Java注解,元注解,自定义注解的使用
|
Java 编译器 测试技术
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit