Spring Boot中validation-api和hibernate-validator详解及快速应用实践,@Valid BindingResult实现接口入参自动检验,Java实体字段校验

2021年9月20日 11点热度 0条评论 来源: csdn-华仔

前言

在项目开发过程中,经常会对一些字段进行校验,比如字段的非空校验、字段的长度校验,以及定制的校验规则等,如果一个工程中存在这些过度的与业务逻辑无关的代码,会让你的代码变的繁重不堪,繁琐的校验,重复的编码,大大降低了我们的工作的效率,而且准确性还不敢保证。

本篇文章主要对validation-api内置的constraintshibernate-validator内置的constraints进行了介绍,通过本篇文章的学习,让你在Java项目开发过程中的数据校验变得优雅且高效。

Spring Boot中validation.constraints注解详解及快速应用实践

一、关于JSR-303规范

JSR-303是JAVA EE6中的一项子规范,validation-api是一套标准(JSR-303),叫做Bean Validation,Hibernate Validator是Bean Validation的参考实现,提供了JSR-303 规范中所有内置constraint的实现,除此之外Hibernate Validator还附加了一些constraint。

 

二、validation-api内置的constraints

如下图:

 

API详细说明

Validation-API 概述
@AssertFalse 被注释的元素必须为 false
@AssertTrue 被注释的元素必须为 true
@DecimalMax 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Digits 被注释的元素必须是一个在可接受范围内的数字
@Email 被注释的元素必须是正确格式的电子邮件地址
@Future 被注释的元素必须是将来的日期
@FutureOrPresent 被注释的元素必须是现在或将来的日期
@Max 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Min 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Negative 被注释的元素必须是一个严格的负数(0为无效值)
@NegativeOrZero 被注释的元素必须是一个严格的负数(包含0)
@NotBlank 被注释的元素同StringUtils.isNotBlank,只作用在String上,在String属性上加上@NotBlank约束后,该属性不能为null且trim()之后size>0
@NotEmpty 被注释的元素同StringUtils.isNotEmpty,作用在集合类上面,在Collection、Map、数组上加上@NotEmpty约束后,该集合对象是不能为null的,并且不能为空集,即size>0
@NotNull 被注释的元素不能是Null,作用在Integer上(包括其它基础类),在Integer属性上加上@NotNull约束后,该属性不能为null,没有size的约束;@NotNull作用在Collection、Map或者集合对象上,该集合对象不能为null,但可以是空集,即size=0(一般在集合对象上用@NotEmpty约束)
@Null 被注释的元素元素是Null
@Past 被注释的元素必须是一个过去的日期
@PastOrPresent 被注释的元素必须是过去或现在的日期
@Pattern 被注释的元素必须符合指定的正则表达式
@Positive 被注释的元素必须严格的正数(0为无效值)
@PositiveOrZero 被注释的元素必须严格的正数(包含0)
@Szie 被注释的元素大小必须介于指定边界(包括)之间

 

三、hibernate-validator内置的constraints

如下图:

API详细说明(hibernate-validator:6.1.7.Final)

hibernate-validator 概述
@CodePointLength 验证字符序列的编码点长度在min和max之间,可以通过设置规范化策略来验证规范化值
@ConstraintComposition 布尔运算符,应用于合成约束注释的所有约束,组合约束注释可以定义组成它的约束的布尔组合,参考ConstraintComposition实现
@CreditCardNumber 被注释元素必须是一个有效的信用卡号码,这是Luhn算法的实现,目的是检查用户的错误,而不是信用卡的有效性
@Currency 参考moneyaryamount和CurrencyUnit实现
@EAN 检查被注释的字符序列是否有效,EAN长度为13,支持的类型是String,当字符串为null被认为有效的
@Email 被注释的字符串必须是正确格式的电子邮件地址【已禁用
@ISBN 检查被注释字符序列是否有效,支持的类型是String,null将被认为有效的,在验证过程中,忽略所有非ISBN字符,所有数字和“X”都被认为是有效的ISBN字符。主要用于证以破折号分隔的ISBN时,这很有用,例如:239-992-190-873-492
@Length 被注释的字符串的长度必须在指定的范围内
@LuhnCheck Luhn算法检查约束,用于验证一系列数字通过Luhn Modulo 10校验算法。Luhn Modulo 10的计算方法是把每一个数字加起来,每个数字都是奇数,数字(从右到左)的值乘以2,如果值大于9的,则结果数字的总和在总和之前,支持的类型是String,null被认为有效的
@Mod10Check 允许验证一系列数字通过Mod10校验和算法。经典的Mod10是通过把每一个奇数加起来计算出来的数字(从右到左)的值乘以乘数,例如:ISBN-13是Modulo 10校验和乘数= 3,在已知的情况下,代码使用乘数的偶数和奇数数字;为了支持这种实现,Mod10约束使用权重选项,它具有与乘数相同的效果,但为偶数数字,支持的类型是String。null被认为有效的
@Mod11Check 允许验证一系列数字通过Mod11校验和算法,对于最常见的Mod11变体的总和计算是通过乘以一个权重最右边的数字(不包括校验数字)到最左边。权重从2开始,每个数字加1。然后结果为11 - (sum % 11)计算校验数字。例如:24187的校验位是3
Sum = 7x2 + 8x3 + 1x4 + 4x5 + 2x6 = 74 
11 - (74% 11) = 11 - 8 = 3,所以“24187-3”是一个有效的字符序列
@ModCheck 被注解的元素表示验证一系列数字通过mod 10或mod 11校验和算法,支持的类型是String,null被认为有效的【已禁用
@Normalized 验证字符序列是否为规范化形式,可以通过设置规范化策略来验证规范化值
@NotBlank 同StringUtils.isNotBlank,只作用在String上,在String属性上加上@NotBlank约束后,该属性不能为null且trim()之后size>0(同validation-api)【已禁用
@NotEmpty 同StringUtils.isNotEmpty,作用在集合类上面,在Collection、Map、数组上加上@NotEmpty约束后,该集合对象是不能为null的,并且不能为空集,即size>0【已禁用
@Range 被注释的元素必须在合适的范围内
@SafeHtml 验证用户提供的富文本,以确保它不包含恶意代码,如嵌入式元素。注意,这个约束假设您想要验证代表HTML文档正文片段的输入。如果你想要验证代表一个完整HTML文档的输入,在校验的白名单中添加HTML、head和body标记【已禁用
@ScriptAssert 类级约束,它对脚本表达式求值注释的元素。此约束可用于实现验证日常活动,依赖于注释元素的多个属性。脚本表达式可以写在任何脚本或表达式语言中,其中的JSR 223兼容的引擎可以在类路径中找到
@UniqueElements 验证集合中的每个对象都是唯一的,即不能找到两个相等的元素集合,这对于JAX-RS很有用,它总是将集合反序列化为一个列表。因此,当重复的将其转换为一个集合时,会被隐式或默认的删除掉
@URL 验证带注释的字符串是一个URL,参数protocol、host和port对应URL的相应部分。可以加上一个额外的正则表达式regexp和flags可以进一步定制URL的验证标准。默认情况下,约束验证使用java.net.URL构造函数来验证字符串,这意味着匹配的协议处理程序需要可用,需要保证程序中以下协议的处理程序在默认JVM-HTTP、HTTPS、FTP文件和JAR中存在的

 

 

 

 

 

 

 

 

 

三、代码示例

1、引入依赖

Spring Boot项目工程依赖,因为在spring-boot-starter-web中已经包含了validation-api和hibernate-validator,所以无需再额外引用

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/>
    </parent>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

Maven项目工程依赖maven

        <!-- validation-api -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <!-- hibernate-validator -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.7.Final</version>
        </dependency>

如果是Spring Boot项目工程,可忽略此步骤,因为在Spring Boot组件内部已经内置了validation-api和hibernate-validator,

 

1、新建实体类

package com.b2c.aiyou.device.inventory.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;

import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * Company:     B2C哎呦商城
 * Department:  研发一组
 * Title:       [b2c哎呦商城 — InventoryEntity 模块]
 * Description: [InventoryEntity 实体类]
 * Created on:  2019-04-23
 * Contacts:    [who.seek.me@java98k.vip]
 *
 * @author huazai
 * @version V1.1.0
 */
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel
public class InventoryEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 类别CODE
     */
    @NotBlank(message = "类别CODE不能为Null")
    @Size(min = 8, max = 32, message = "类别CODE长度必须在{min}~{max}之间")
    @ApiModelProperty(value = "类别CODE")
    private String categoryCode;
    /**
     * 类型CODE
     */
    @NotBlank(message = "类型CODE不能为Null")
    @Size(min = 8, max = 32, message = "类型长度必须在{min}~{max}之间")
    @ApiModelProperty(value = "类型CODE")
    private String typeCode;
    /**
     * 最高库存量
     */
    @DecimalMax(value = "100000", message = "最高库存量必须小于或等于{value}")
    @ApiModelProperty(value = "最高库存量")
    private Integer inventoryMax;
    /**
     * 最低库存量
     */
    @DecimalMin(value = "100", message = "最低库存量必须大于或等于{value}")
    @ApiModelProperty(value = "最低库存量")
    private Integer inventoryMin;
    /**
     * 最小进货量
     */
    @Min(value = 100, message = "最小进货量必须大于或等于{value}")
    @ApiModelProperty(value = "最小进货量")
    private Integer restockMin;
    /**
     * 最大进货量
     */
    @Max(value = 10000, message = "最大进货量必须小于或等于{value}")
    @ApiModelProperty(value = "最大进货量")
    private Integer restockMax;
    /**
     * 进货日期
     */
    @Future(message = "进货日期必须大于当前日期")
    @ApiModelProperty(value = "进货日期")
    private Date restockTime;
    /**
     * 进货周期
     */
    @NotEmpty(message = "进货周期不能为Null")
    @ApiModelProperty(value = "进货日期")
    private List<String> periodTime;
    /**
     * 备注
     */
    @ApiModelProperty(value = "备注")
    @Size(min = 50, max = 500, message = "备注内容必须在{min}~{max}之间")
    private String remark;


}

 

2、新建Controller类

package com.b2c.aiyou.device.inventory.dto;

import com.b2c.aiyou.device.common.annotation.Log;
import com.b2c.aiyou.device.inventory.service.IInventoryService;
import com.b2c.aiyou.common.constant.ResultCodeEnum;
import com.b2c.aiyou.common.result.AppException;
import com.b2c.aiyou.common.result.JSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;


/**
 * Company:     B2C哎呦商城
 * Department:  研发一组
 * Title:       [b2c哎呦商城 — InventoryController 模块]
 * Description: [InventoryController 控制层]
 * Created on:  2019-04-23
 * Contacts:    [who.seek.me@java98k.vip]
 *
 * @author huazai
 * @version V1.1.0
 */
@Slf4j
@RestController
@Api(value = "InventoryEntity 控制器")
@RequestMapping("/inventory")
public class InventoryController {

    @Autowired
    private IInventoryService inventoryService;

    /**
     * Description:[通过实体添加新的库存数据]
     *
     * @param inventoryDTO [实体信息]
     * @return JSONResult
     * @date 2020-04-23
     * @author huazai
     */
    @PostMapping("/addInventory")
    @ApiOperation(value = "/addInventory", notes = "通过实体添加新的库存数据")
    @Log(operation = "通过实体添加新的库存数据")
    public JSONResult addInventory(@Valid @RequestBody @ApiParam(name = "InventoryDTO", value = "InventoryDTO 实体类") InventoryDTO inventoryDTO, BindingResult bindingResult) {
        try {
            // 字段校验
            if (bindingResult.hasErrors()) {

                return JSONResult.failure(bindingResult);
            }

            // TODO 其它校验

            int result = this.inventoryService.insertInventory(inventoryDTO);

            if (result > 0) {

                // 成功处理逻辑
                return JSONResult.success();
            } else {

                // 失败处理逻辑
                return JSONResult.failure();
            }
        } catch (AppException e) {

            log.info("异常信息:{}", e);
            return JSONResult.failure(ResultCodeEnum.Failure.getCode(), e.getMessage());
        }

    }

}

 

注意:在需要校验的对象后面,必须添加BindingResult来接收校验结果,并对校验结果进行处理bindingResult.hasErrors(),否则校验无意义。

两种验证都是可行的,但博主现在一般使用validation-api了,偶尔会使用hibernate-validator,但都很少才能用到!

 

四、总结

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring的JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),通过BindingResult对象可以直接获取参数验证结果。

org.springframework.validation.annotation.Validated
javax.validation.Valid

在校验Controller接口的入参时,使用@Validated或者@Valid在基本没有太大的区别。但是在分组、注解位置、嵌套验证等功能上略有所不同,区别如下:

1、分组校验支持

关于参数的分组校验,在同一个对象中同一个成员属性可能出现因业务的不同,而需要进行不同的校验规则,如果没有分组校验就需要额外的新增一个类似的校验对象,很显然这是多余的,通过分组校验,可以实现对不同的校验规则进行隔离,互相不受影响,分组校验可以很好的提高接口参数校验的灵活性。

@Validated:提供了一个分组功能,可以在入参校验时,通过@Validated根据不同的分组采用不同的校验机制;
@Valid:作为标准JSR-303规范,以前的老版本没有加入分组校验的功能,不过博主看现在最新版本已经加入了分组校验功能了;

2、注解位置不同
@Validated:可作用在基本类型、报装类型、方法和方法参数上,但是不作用在成员属性(成员变量)上;
@Valid:可作用在方法、构造函数、方法参数和成员属性(成员变量)上;

3、不同的嵌套校验

@Validated:目前博主看了一下,好像还不支持嵌套校验的;

@Valid:可作用在校验类的子类上,进行嵌套校验;

 

 

 

 

 

参考:

JSR 303: Bean Validation

Hibernate Validator

 好了,关于 Spring Boot中validation-api和hibernate-validator详解及快速应用实践,@Valid BindingResult实现接口入参自动检验,Java实体字段校验 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。 
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。

作       者: 华    仔
联系作者: who.seek.me@java98k.vip
来        源: CSDN (Chinese Software Developer Network)
原        文: https://blog.csdn.net/Hello_World_QWP/article/details/116129788
版权声明: 本文为博主原创文章,请在转载时务必注明博文出处!
    原文作者:csdn-华仔
    原文地址: https://blog.csdn.net/Hello_World_QWP/article/details/116129788
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。