springboot入参自动校验(validation)

使用validation-apihibernate-validator实现接口入参自动检验

信息来源

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

前言

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

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

依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

spring-boot-starter-web中已经包含 validation-apihibernate-validator

示例

有一个接收体

@Data
public class LoginBody {
    /**
    * 用户名.
    */
    @NotBlank(message = "用户名不能为空")
    private String username;

    /**
    * 用户密码.
    */
    @NotBlank(message = "密码不能为空")
    private String password;
}

接口中这样写

    @PostMapping("/login")
    public void login(@RequestBody @Validated final LoginBody user) {}

分组校验示例

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

使用示例

判断是哪个分组的校验需要传一个 class来标识,所以我们封装一个标识类

/**
 * 分组校验类.
 *
 * @author 钟舒艺
 **/
public class ValidationGroups {

    /**
     * 添加时校验.
     */
    public interface Add {
    }

    /**
     * 编辑时校验.
     */
    public interface Edit {
    }

    /**
     * 查询时校验.
     */
    public interface Query {
    }

    /**
     * 添加时校验标识.
     */
    public static final Class<ValidationGroups.Add> ADD = ValidationGroups.Add.class;
    /**
     * 修改时校验标识.
     */
    public static final Class<ValidationGroups.Add> EDIT = ValidationGroups.Add.class;
    /**
     * 查询时校验标识.
     */
    public static final Class<ValidationGroups.Add> QUERY = ValidationGroups.Add.class;

}

新增与修改是最常用的功能,当修改时需要验证前端是否传了id,而新增时不校验id
接收类中这样写

    @NotNull(message = "主键不能为空", groups = {ValidationGroups.EDIT})
    @Min(value = 0, message = "id最低为0", groups = {ValidationGroups.EDIT})
    private Long id;

接口中写上分组信息

// 新增接口
@PostMapping
public CommonResult<Void> addUser(
    @Validated({ValidationGroups.ADD}) @RequestBody final UserDTO user) {}

@PutMapping
public CommonResult<Void> edit(
    @Validated({ValidationGroups.EDIT}) @RequestBody final UserDTO user) {}

这样就可以在新增时不校验id 而在修改时校验id 是否存在与合法

全局异常处理

如果没有处理抛出的异常,那会直接跳到错误界面,前端不好处理。
所以我们需要处理这些异常,返回给前端详细的信息

import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * 全局异常处理程序.
 *
 * @author 钟舒艺
 */
@Slf4j
@ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 参数验证异常.
     *
     * @param ex MethodArgumentNotValidException
     * @return 通用返回类
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult<Void> parameterValidationException(
            final MethodArgumentNotValidException ex
    ) {
        log.error(ex.getMessage(), ex);
        final BindingResult result = ex.getBindingResult();
        final StringBuilder errorMsg = new StringBuilder();
        if (result.hasErrors()) {
            final List<FieldError> fieldErrors = result.getFieldErrors();
            fieldErrors.forEach(error -> errorMsg.append(error.getDefaultMessage()).append("! ;"));
        }
        log.error("参数验证异常,{}", ex.getMessage());
        return CommonResult.failed(cn.hutool.http.HttpStatus.HTTP_BAD_REQUEST, errorMsg.toString());
    }
}

@Validated@Valid的区别

  1. @Valid@Validated 都是用来校验接收参数的。
  2. @Valid 是使用Hibernate validation的时候使用
  3. @Validated是使用 Spring Validator 校验机制使用
  4. @Validated可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上,不支持嵌套检测
  5. @Valid可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套检测
  6. @Validated提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。没有添加分组属性时,默认验证没有分组的验证属性。

validation-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 注解

hibernate-validator概述
@CodePointLength验证字符序列的编码点长度在min和max之间,可以通过设置规范化策略来验证规范化值
@ConstraintComposition布尔运算符,应用于合成约束注释的所有约束,组合约束注释可以定义组成它的约束的布尔组合,参考ConstraintComposition实现
@CreditCardNumber被注释元素必须是一个有效的信用卡号码,这是Luhn算法的实现,目的是检查用户的错误,而不是信用卡的有效性
@Currency参考moneyaryamount和CurrencyUnit实现
@EAN检查被注释的字符序列是否有效,EAN长度为13,支持的类型是String,当字符串为null被认为有效的
@ISBN检查被注释字符序列是否有效,支持的类型是String,null将被认为有效的,在验证过程中,忽略所有非ISBN字符,所有数字和“X”都被认为是有效的ISBN字符。主要用于证以破折号分隔的ISBN时,这很有用,例如:239-992-190-873-492
@Length被注释的字符串的长度必须在指定的范围内
@LuhnCheckLuhn算法检查约束,用于验证一系列数字通过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”是一个有效的字符序列
@Normalized验证字符序列是否为规范化形式,可以通过设置规范化策略来验证规范化值
@Range被注释的元素必须在合适的范围内
@ScriptAssert类级约束,它对脚本表达式求值注释的元素。此约束可用于实现验证日常活动,依赖于注释元素的多个属性。脚本表达式可以写在任何脚本或表达式语言中,其中的JSR 223兼容的引擎可以在类路径中找到
@UniqueElements验证集合中的每个对象都是唯一的,即不能找到两个相等的元素集合,这对于JAX-RS很有用,它总是将集合反序列化为一个列表。因此,当重复的将其转换为一个集合时,会被隐式或默认的删除掉
@URL验证带注释的字符串是一个URL,参数protocol、host和port对应URL的相应部分。可以加上一个额外的正则表达式regexp和flags可以进一步定制URL的验证标准。默认情况下,约束验证使用java.net.URL构造函数来验证字符串,这意味着匹配的协议处理程序需要可用,需要保证程序中以下协议的处理程序在默认JVM-HTTP、HTTPS、FTP文件和JAR中存在的
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值