当前位置:首页|资讯

关于 Spring 中 Hibernate Validator 的使用

作者:_YKI发布时间:2024-10-13

最近在(第n次)尝试学blender,看看能不能拿来做构图和光影的参考,提供素描素材之类的。

之前学习过hibernate Validator,但当时做的笔记弄丢了,最近可能又要开始写Java,所以把这一部分学习一下,做个笔记。

Hibernate Validator用于值校验,能够避免业务中过多地出现校验业务代码。Hibernate Validator支持对自定义类型,集合类型和内置Java类型进行校验,同时支持定义校验组,即让类型约束从属于特定组,只在进行该组的校验时才发挥作用。

为什么要使用Hibernate Validator:

  1. 声明式,避免到处写丑陋的样板代码

  2. 更清晰地规范实体定义,校验注解本身就是一种注释

  3. 支持自定义校验逻辑和错误消息,其中还能注入依赖以实现更复杂逻辑

环境搭建和基本使用

通过下面的starter引入hibernate validator依赖:

Hibernate Validator利用切面完成自己的逻辑,它根据上的@Validated注解去进行切入,并根据参数上的注解去进行校验。总的来说,要让校验起效,需要:

  1. spring-boot-starter-validation依赖引入

  2. 类上标注@Validated(**要且必须要在类上标注!必须是@Validated而非@Valid**)

  3. 方法的参数上标注相应校验注解(其中,实体类参数使用@Valid@Validated注解)

  4. 调用方法时不能从内部调用(切面嘛,懂的都懂)

注意,Hibernate Validator通过切面工作,因此它不仅能切入Controller,也能切入Service,但仅此而已,某些时候还是需要手动进行校验。

下面是一个极简例子,涉及到控制器和实体类的校验,它已经提出了许多要注意的部分:

@Valid校验能进行嵌套校验,但它会直接忽略掉null,因此上面的SomeDto定义中,next字段为null,以及dtos字段为null或空集合,或集合中存在null,都是容忍的。

Controller的参数校验似乎并非完全是通过切面完成的——即使Controller上未加@Validated注解,@Valid注解仍旧会生效,但是其它校验注解不会生效,因此最佳实践是,总是在类上加@Validated,不要嫌麻烦

关于Service的校验

Hibernate Validator有一条规则:A method overriding another method must not redefine the parameter constraint configuration,它是说,子类无法覆盖掉父类上的校验注解,即使父类上没有校验注解

上面的规则同时暗示了,Hibernate Validator的注解是能够继承的。一般而言,Spring项目中Service的接口和实现是分离的,如果要校验Service,根据上面的规则,我们应当:

  1. 在接口上标注@Validated注解

  2. 在接口上的方法参数中添加相应校验注解

  3. 在实现上不需要添加任何注解,或者保证实现上的注解和接口上的完全相同

初看感觉这个要求不太合理,但细想其实还好——按理来说,接口内部使用何种实现对接口的调用者是透明的,因此值的约束必须是定义自接口层级上的,实现对值的约束只能更宽,不能更窄,而我们无法判断约束的宽窄,所以就硬性要求它们保持一致。但其实作为业务的开发者来说,还是希望能够将注解只写在实现上。

常用校验注解

这里列出可能会常用的注解,注意几乎所有注解都认为null是合法的。注解主要在org.hibernate.validator.constraintsjavax.validation.constraints包下。

手动校验实体类

有时候可能会想要进行手动校验,比如我们可能会想写mybatis拦截器,在插入和更新数据前进行校验,手动校验可以利用Spring提供的 Beanjavax.validation.Validator(这是它对JSR规范的实现),它返回”vioiations”,即实体对象对约束的违反。

拦截校验异常

Hibernate Validator会抛出如下异常:

  1. org.springframework.web.bind.MethodArgumentNotValidException,抛出在Controller的@Valid@Validated注解的实体类参数(这简直就是历史遗留问题),默认响应码是400,消息是”Bad Request”

  2. javax.validation.ConstraintViolationException,其他情况,默认响应码是500,消息是”Internal Server Error”,这是符合道理的——控制器的参数错误是用户的错误,服务层的参数错误是开发者的错误

这两个异常都需要被拦截才能妥善把校验信息响应给前端……但这样真的好吗?全给到前端不是会让坏家伙有可乘之机吗?总之贴上(实际操作时应当像ruoyi那样,正常响应和错误响应形式保持一致):

自定义校验注解

有时候需要自定义校验,比如要校验身份证,这个肯定没有被提供,需要手写。这时候需要自定义Validator和注解。

Validator不关心它被标注在字段上还是标注到参数上,它直接拿到值然后去做校验,但同时也允许获取当前的字段路径等信息以构建错误消息

Spring Boot会惰性地为为每个不同的注解创建相应的Validator实例,从而让每个Validator都负责同一个注解(类型相同,且所有字段值相同),避免任何并发问题,同时支持在创建Validator时注入依赖。

编写自定义校验注解需要:

  1. 创建自定义注解,注解需要是Runtime的,需要能够标注到字段和参数上(也可以让注解能标注到类上,这允许对整个类进行校验),注解需要引用下一步中编写的自定义Validator,注解必须包含groups,message, payload字段(可以直接抄现成的)

  2. 创建自定义Validator,如果注解能够校验多种类型,则每个类型都需要一个Validator,Validator类要实现ConstraintValidator<注解, T>在构造函数中注入Spring Bean依赖,在initialize方法中注入注解,并在isValid中线程安全地进行校验

  3. 实现isValid时,第一个参数是字段值,第二个参数是当前上下文,isValid方法返回true或false,true表示校验通过,false表示不通过,此时hibernate validator会根据上下文去构造相应violation,此时可以自定义错误消息。

下面编写一个身份证校验注解,其中演示了如何在校验过程中获取Bean使得能和系统其他部分进行交互,以及如何修改错误消息。

分组校验

所有校验注解都有groups参数(除了Validated,它直接用value),表示校验所属的校验组。在进行校验时,通过@Validated的value参数指定只校验特定组的注解(注意它标识在参数上时表示只校验这些组的注解,标识在字段上时表示该校验属于这些组,这是两种不同的语义)。校验组使用任意Class进行标识,这些Class不需要任何实际操作。

这里又有一个权衡——是让不同组正交,在使用时选择校验多个组,还是让每个组都负责特定具体业务,校验时只校验一个组?按状况判断吧。


Copyright © 2024 aigcdaily.cn  北京智识时代科技有限公司  版权所有  京ICP备2023006237号-1