分组序列@GroupSequenceProvider、@GroupSequence控制数据校验顺序,解决多字段联合逻辑校验问题【享学Spring MVC】(下)

简介: 分组序列@GroupSequenceProvider、@GroupSequence控制数据校验顺序,解决多字段联合逻辑校验问题【享学Spring MVC】(下)

4、determineGroupValidationOrder(groups)从调用者指定的分组里确定组序列(组的执行顺序)


ValidatorImpl:
  @Override
  public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
    ...
    BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass );
    ...
    ... // 准备好ValidationContext(持有rootBeanMetaData和object实例)
    // groups是调用者传进来的分组数组(对应Spring MVC中指定的Group信息~)
    ValidationOrder validationOrder = determineGroupValidationOrder(groups);
    ... // 准备好ValueContext(持有rootBeanMetaData和object实例)
    // 此时还是Bean级别的,开始对此bean执行校验
    return validateInContext( validationContext, valueContext, validationOrder );
  }
  private ValidationOrder determineGroupValidationOrder(Class<?>[] groups) {
    Collection<Class<?>> resultGroups;
    // if no groups is specified use the default
    if ( groups.length == 0 ) {
      resultGroups = DEFAULT_GROUPS;
    } else {
      resultGroups = Arrays.asList( groups );
    }
    // getValidationOrder()主要逻辑描述。此时候resultGroups 至少也是个[Default.class]
    // 1、如果仅仅只是一个Default.class,那就直接return
    // 2、遍历所有的groups。(指定的Group必须必须是接口)
    // 3、若遍历出来的group标注有`@GroupSequence`注解,特殊处理此序列(把序列里的分组们添加进来)
    // 4、普通的Group,那就new Group( clazz )添加进`validationOrder`里。并且递归插入(因为可能存在父接口的情况)
    return validationOrderGenerator.getValidationOrder( resultGroups );
  }


到这ValidationOrder(实际为DefaultValidationOrder)保存着调用者调用validate()方法时传入的Groups们。分组序列@GroupSequence在此时会被解析。

到了validateInContext( ... )就开始拿着这些Groups分组、元信息开始对此Bean进行校验了~


5、validateInContext( ... )在上下文(校验上下文、值上下文、指定的分组里)对此Bean进行校验

ValidatorImpl:
  private <T, U> Set<ConstraintViolation<T>> validateInContext(ValidationContext<T> validationContext, ValueContext<U, Object> valueContext, ValidationOrder validationOrder) {
    if ( valueContext.getCurrentBean() == null ) { // 兼容整个Bean为null值
      return Collections.emptySet();
    }
    // 如果该Bean头上标注了(需要defaultGroupSequence处理),那就特殊处理一下
    // 本例中我们的Person肯定为true,可以进来的
    BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
    if ( beanMetaData.defaultGroupSequenceIsRedefined() ) {
      // 注意此处又调用了beanMetaData.getDefaultGroupSequence()这个方法,这算是二次调用了
      // 此处传入的Object哟~这就解释了为何在判空里面的 `年龄为:xxx`被打印了两次的原因
      // assertDefaultGroupSequenceIsExpandable方法是个空方法(默认情况下),可忽略
      validationOrder.assertDefaultGroupSequenceIsExpandable( beanMetaData.getDefaultGroupSequence( valueContext.getCurrentBean() ) );
    }
    // ==============下面对于执行顺序,就很重要了===============
    // validationOrder装着的是调用者指定的分组(解析分组序列来保证顺序~~~)
    // 需要特别注意:光靠指定分组,是无序的(不能保证校验顺序的) 所以若指定多个分组需要小心求证
    Iterator<Group> groupIterator = validationOrder.getGroupIterator();
    // 按照调用者指定的分组(顺序),一个一个的执行分组校验。
    while ( groupIterator.hasNext() ) {
      Group group = groupIterator.next();
      valueContext.setCurrentGroup(group.getDefiningClass()); // 设置当前正在执行的分组
      // 这个步骤就稍显复杂了,也是核心的逻辑之一。大致过程如下:
      // 1、拿到该Bean的BeanMetaData
      // 2、若defaultGroupSequenceIsRedefined()=true  本例Person标注了provder注解,所以有指定的分组序列的
      // 3、根据分组序列的顺序,挨个执行分组们(对所有的约束MetaConstraint都顺序执行分组们)
      // 4、最终完成所有的MetaConstraint的校验,进而完成此部分所有的字段、方法等的校验
      validateConstraintsForCurrentGroup( validationContext, valueContext );
      if ( shouldFailFast( validationContext ) ) {
        return validationContext.getFailingConstraints();
      }
    }
    ... // 和上面一样的代码,校验validateCascadedConstraints
    // 继续遍历序列:和@GroupSequence相关了
    Iterator<Sequence> sequenceIterator = validationOrder.getSequenceIterator();
    ...
    // 校验上下文的错误消息:它会把本校验下,所有的验证器上下文ConstraintValidatorContext都放一起的
    // 注意:所有的校验注解之间的上下文ConstraintValidatorContext是完全独立的,无法互相访问通信
    return validationContext.getFailingConstraints();
  }


that is all. 到这一步整个校验就完成了,若不快速失败,默认会拿到所有校验失败的消息。


真正执行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) {
    boolean isValid;
    // 解析出value值
    V validatedValue = (V) valueContext.getCurrentValidatedValue(); 
    // 把value值交给校验器的isValid方法去校验~~~
    isValid = validator.isValid(validatedValue,constraintValidatorContext);
    ...
    if (!isValid) {
      // 校验没通过就使用constraintValidatorContext校验上下文来生成错误消息
      // 使用上下文是因为:毕竟错误消息可不止一个啊~~~
      // 当然此处借助了executionContext的方法~~~内部其实调用的是constraintValidatorContext.getConstraintViolationCreationContexts()这个方法而已
      return executionContext.createConstraintViolations(valueContext, constraintValidatorContext);
    }
  }
}


至于上下文ConstraintValidatorContext怎么来的,是new出来的:new ConstraintValidatorContextImpl( ... ),每个字段的一个校验注解对应一个上下文(一个属性上可以标注多个约束注解哦~),所以此上下文是有很强的隔离性的。


ValidationContext<T> validationContext和ValueContext<?, Object> valueContext它哥俩是类级别的,直到ValidatorImpl.validateMetaConstraints方法开始一个一个约束器的校验~


自定义注解中只把ConstraintValidatorContext上下文给调用者使用,而并没有给validationContext和valueContext,我个人觉得这个设计是不够灵活的,无法方便的实现dependOn的效果~


ConstraintValidatorContext一般它能用于在代码里个性化错误消息ConstraintViolation。可以这么来做:


        context.disableDefaultConstraintViolation(); // 禁用注解上默认的模版消息
        // context.getDefaultConstraintMessageTemplate(); // 未插值的消息模版
        //context.buildConstraintViolationWithTemplate("这是我的错误消息模版")
        //        .addParameterNode()
        //        .addPropertyNode()
        //        .addContainerElementNode()
        //        .addConstraintViolation();


若你想要更多的功能,可使用子接口HibernateConstraintValidatorContext提供的方法。参见这里:【小家Java】深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)


解决网友的问题


我把这部分看似是本文最重要的引线放到最后,是因为我觉得我的描述已经解决这一类问题,而不是只解决了这一个问题。


回到文首截图中热心网友反应的问题,只要你阅读了本文,我十分坚信你已经有办法去使用Bean Validation优雅的解决了。如果各位没有意见,此处我就略了~


总结


本文讲述了使用@GroupSequenceProvider来解决多字段联合逻辑校验的这一类问题,这也许是曾经很多人的开发痛点,希望本文能帮你一扫之前的障碍,全面拥抱Bean Validation吧~

本文我也传达了一个观点:相信流行的开源东西的优秀,不是非常极端的case,深入使用它能解决你绝大部分的问题的。


相关文章
|
1月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
40 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
134 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
3月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
3月前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
3月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
3月前
|
Java Spring 容器
建模底层逻辑问题之以Spring IOC容器为例,使用因果法建模,如何操作
建模底层逻辑问题之以Spring IOC容器为例,使用因果法建模,如何操作
下一篇
无影云桌面