1、是什么?

它简化了 Java Bean Validation 的集成。Java Bean Validation 通过 JSR 380,也称为 Bean Validation 2.0,是一种标准化的方式,用于在 Java 应用程序中对对象的约束进行声明式验证。它允许开发人员使用注解来定义验证规则,并自动将规则应用于相应的字段或方法参数

为了我们方便地使用参数校验功能了

2、怎么玩?

(1) 首先导入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.ly</groupId>
<artifactId>springboot-validate</artifactId>
<version>1.0-SNAPSHOT</version> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
</parent> <properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
(2) 在对应实体上加@Validated注解,开启校验规则
package com.ly.valid.controller;

import com.ly.valid.common.R;
import com.ly.valid.common.ResultCodeEnum;
import com.ly.valid.entity.Person;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 21:01
* @tags 喜欢就去努力的争取
*/
@RestController
public class TestController { /**
* 保存
*/
@PostMapping("/test1")
public R save(@Validated @RequestBody Person person, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
Map<String, String> map = new HashMap<>(fieldErrorList.size());
fieldErrorList.forEach(item -> {
String message = item.getDefaultMessage(); // 如果没有在相应的注解中加message,则会获取默认信息 例如:@NotBlank(message = "品牌名必须提交"),获取的message则为品牌名必须提交
String field = item.getField(); // 获取哪个字段出现的问题
map.put(field, message);
});
return R.fail(ResultCodeEnum.PARAM_ERROR, map);
} else {
// 伪代码
// personService.save(person);
}
return R.success();
} /**
* 全局处理
*
* @param person
* @return
*/
@PostMapping("/test2")
public R test(@Validated Person person) {
return R.success(person);
} }
(3) 给对应的实体Bean添加校验注解,并自定义message提示
package com.ly.valid.entity;

import com.ly.valid.constant.RegularConstant;
import jakarta.validation.constraints.*;
import lombok.Data; import java.time.LocalDate; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 21:03
* @tags 喜欢就去努力的争取
*/
@Data
public class Person { @NotBlank(message = "name 姓名不能为空")
private String name; @NotNull(message = "age 年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
private Integer age; @NotNull(message = "gender 性别不能为空")
private Integer gender; @Email(regexp = RegularConstant.EMAIL, message = "email 邮箱格式不正确")
private String email; @Pattern(regexp = RegularConstant.PHONE, message = "phone 手机号格式不正确")
private String phone; @Past(message = "birthday 生日日期有误")
private LocalDate birthday; }
(4) 不加@Validate注解,测试一下

(5) 开启校验功能@Validated:不使用自定义message会有默认提示信息

(6) 方式一: 我想自定义提示信息怎么整?简单,只需要给响应的校验bean后面紧跟着添加一个BindingResult,就可以获取到校验的结果了
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
Map<String, String> map = new HashMap<>();
fieldErrorList.forEach(item -> {
String message = item.getDefaultMessage(); // 如果没有在相应的注解中加message,则会获取默认信息 例如:@NotBlank(message = "品牌名必须提交"),获取的message则为品牌名必须提交
String field = item.getField(); // 获取哪个字段出现的问题
map.put(field, message);
});
return R.error(400, "提交的数据不合法").put("data", map);
} else {
brandService.save(brand);
}
return R.ok();
}
(7) 测试一下

(8) 方式二:如果有很多需要这样手动一个个处理,就显得很麻烦了;所以我们需要一个全局处理
package com.ly.valid.common;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.List;
import java.util.stream.Collectors; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 22:13
* @tags 喜欢就去努力的争取
*/
@RestControllerAdvice
public class GlobalExceptionHandler { /**
* 处理 form data方式调用接口校验失败抛出的异常 (对象参数)
*/
@ExceptionHandler(BindException.class)
public R error(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> errorMessages = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.toList();
return R.fail(ResultCodeEnum.PARAM_ERROR.getCode(), errorMessages.toString());
} /**
* 处理 json 请求体调用接口校验失败抛出的异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R error(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> errorMessages = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.toList();
return R.fail(ResultCodeEnum.PARAM_ERROR.getCode(), errorMessages.toString());
} /**
* 单个参数校验失败抛出的异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R error(ConstraintViolationException e) {
String errorMsg = e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessageTemplate)
.collect(Collectors.joining());
return R.fail(errorMsg);
}
}
(9) 测试一下

3、相关注解信息

注解 数据类型 说明
@NotBlank CharSequence 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@NotEmpty CharSequence,Collection,Map,Arrays 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Length(min=下限, max=上限) CharSequence 验证注解的元素值长度在min和max区间内
@NotNull 所有类型 验证注解的元素值不是null
@Null 所有类型 验证注解的元素值是null
@Max(value=n) BigDecimal,BigInteger,byte,short,int,long和原始类型的相应包装。HV额外支持:CharSequence的任何子类型(评估字符序列表示的数字值),Number的任何子类型。 验证注解的元素值小于等于@Max指定的value值
@Min(value=n) BigDecimal,BigInteger,byte,short,int,long和原始类型的相应包装。HV额外支持:CharSequence的任何子类型(评估char序列表示的数值),Number的任何子类型。 验证注解的元素值大于等于@Min指定的value值
@Size(min=最小值, max=最大值) 字符串,集合,映射和数组。HV额外支持:CharSequence的任何子类型。 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Email CharSequence 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
@Pattern(regex=正则表达式, flag=) CharSequence 验证注解的元素值与指定的正则表达式匹配
@Range(min=最小值, max=最大值 CharSequence, Collection, Map and Arrays, BigDecimal, BigInteger, CharSequece, byte, short, int, long以及原始类型各自的包装 验证注解的元素值在最小值和最大值之间
@AssertFalse Boolean, boolean 验证注解的元素值是false
@AssertTrue Boolean, boolean 验证注解的元素值是true
@DecimalMax(value=n) BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。 验证注解的元素值小于等于@ DecimalMax指定的value值
@DecimalMin(value=n) BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。 验证注解的元素值小于等于@ DecimalMin指定的value值
@Digits(integer=整数位数, fraction=小数位数) BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。 验证注解的元素值的整数位数和小数位数上限
@Future java.util.Date,java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate; Additionally supported by HV, if the Joda Time date/time API is on the classpath: any implementations of ReadablePartial and ReadableInstant 验证注解的元素值(日期类型)比当前时间晚
@Past java.util.Date,java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate; ,则由HV附加支持:ReadablePartial和ReadableInstant的任何实现。 验证注解的元素值(日期类型)比当前时间早
@Valid Any non-primitive type(引用类型) 验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象

注:HV ---> Hibernate-Validator

4、分组校验(当我们新增和修改字段时,有可能校验规则是不一样的,那么该如何处理呢?)

(1) 定义分组信息(唯一即可)
package com.ly.valid.group;

/**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 23:10
* @tags 喜欢就去努力的争取
*/
public interface AddGroup {
} package com.ly.valid.group; import jakarta.validation.groups.Default; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 23:10
* @tags 喜欢就去努力的争取
*/
public interface UpdateGroup extends Default {
}
(2) 在分组参数后指定它的groups,需要指定在什么情况下需要进行校验,类型是一个接口数组
package com.ly.valid.entity;

import com.ly.valid.constant.RegularConstant;
import com.ly.valid.group.AddGroup;
import com.ly.valid.group.UpdateGroup;
import jakarta.validation.constraints.*;
import jakarta.validation.groups.Default;
import lombok.Data; import java.time.LocalDate; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-11 21:03
* @tags 喜欢就去努力的争取
*/
@Data
public class Person { @NotBlank(message = "name 姓名不能为空", groups = AddGroup.class)
private String name; @NotNull(message = "age 年龄不能为空", groups = UpdateGroup.class)
@Min(value = 0, message = "年龄不能小于0")
private Integer age; @NotNull(message = "gender 性别不能为空", groups = {AddGroup.class, UpdateGroup.class})
private Integer gender; @NotBlank(message = "email 邮箱不能为空", groups = Default.class)
@Email(regexp = RegularConstant.EMAIL, message = "email 邮箱格式不正确", groups = Default.class)
private String email; @NotBlank(message = "phone 手机号不能为空", groups = UpdateGroup.class)
@Pattern(regexp = RegularConstant.PHONE, message = "phone 手机号格式不正确")
private String phone; @Past(message = "birthday 生日日期有误")
private LocalDate birthday;
}
(3) 然后再我们的controller层参数前加上@Validated(value = {UpdateGroup.class})注解,指定它是哪一组
/**
* 测试添加分组
*
* @param person
* @return
*/
@PostMapping("/testAddGroup")
public R testAddGroup(@Validated(AddGroup.class) Person person) {
return R.success(person);
} /**
* 测试修改分组
*
* @param person
* @return
*/
@PostMapping("/testUpdateGroup")
public R testUpdateGroup(@Validated(UpdateGroup.class) Person person) {
return R.success(person);
} /**
* 测试添加和修改分组
*
* @param person
* @return
*/
@PostMapping("/testAddAndUpdateGroup")
public R testAddAndUpdateGroup(@Validated({UpdateGroup.class, AddGroup.class}) Person person) {
return R.success(person);
} /**
* 测试默认分组
*
* @param person
* @return
*/
@PostMapping("/testDefaultGroup")
public R testDefaultGroup(@Validated(Default.class) Person person) {
return R.success(person);
}
(4) 测试AddGroup

(5) 测试UpdateGroup

注意:细心的同学发现了,为什么email邮箱是Default.class,修改的时候也触发了呢?原因如下图:

(6) 测试AddAndUpdateGroup

(7) 测试DefaultGroup

注意:默认没有指定分组的情况下@NotBlank,在分组校验的情况下@Validated(value = {AddGroup.class})不生效

5、自定义校验规则

例如:现在我有一个字段gender它的取值就三种0:保密 1:男 2:女,像这种有限个数的枚举值我们该如何去限制呢?这就要使用到的自定义校验注解了

(1) 自定义校验注解
package com.ly.valid.anno;

import jakarta.validation.Constraint;
import jakarta.validation.Payload; import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-12 0:27
* @tags 喜欢就去努力的争取
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue { // 默认错误消息
String message() default "{my.enum-value.err.msg}";
// String message() default ENUM_VALUE_MESSAGE; String[] strValues() default {}; int[] intValues() default {}; // 分组
Class<?>[] groups() default {}; // 负载,可以增加自定义校验逻辑
Class<? extends Payload>[] payload() default {}; // 指定多个时使用
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
}
(2) 自定义校验器
package com.ly.valid.anno;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set; /**
* 枚举值校验注解处理类
*
* @author ly (个人博客:https://www.cnblogs.com/ybbit)
* @date 2023-12-12 0:34
* @tags 喜欢就去努力的争取
*/
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> { /**
* 字符串
*/
private final Set<String> strValueSet = new HashSet<>(); /**
* 数值
*/
private final Set<Integer> intValueSet = new HashSet<>(); @Override
public void initialize(EnumValue constraintAnnotation) {
strValueSet.addAll(Arrays.asList(constraintAnnotation.strValues()));
for (int i : constraintAnnotation.intValues()) {
intValueSet.add(i);
}
} @Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value instanceof Integer) {
// 整数值类型
return intValueSet.contains(value);
} else if (value instanceof String) {
// 字符串类型
return strValueSet.contains(value);
}
return false;
}
}
(3) 关联自定义的校验器和自定义的校验注解

注意:这里的ValidationMessages.properties是数据校验国际化配置文件;名字必须是这个

my.enum-value.err.msg=性别字段值只能为0、1、2

@EnumValue(intValues = {0, 1, 2}, groups = AddGroup.class)
@NotNull(message = "gender 性别不能为空", groups = {AddGroup.class, UpdateGroup.class})
private Integer gender;

(4) 测试一下

6、paload() 负载的用法

// TODO 未完待续

SpringBoot-Validation优雅实现参数校验的更多相关文章

  1. SpringBoot Validation优雅的全局参数校验

    前言 我们都知道在平时写controller时候,都需要对请求参数进行后端校验,一般我们可能会这样写 public String add(UserVO userVO) { if(userVO.getA ...

  2. 一起来学SpringBoot(十七)优雅的参数校验

    参数校验在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动方法内代码显得冗长每次要看哪些参数 ...

  3. 【快学springboot】4.接口参数校验

    前言 在开发接口的时候,参数校验是必不可少的.参数的类型,长度等规则,在开发初期都应该由产品经理或者技术负责人等来约定.如果不对入参做校验,很有可能会因为一些不合法的参数而导致系统出现异常. 上一篇文 ...

  4. 如何使用java validation api进行参数校验----Hibernate-Validation

    在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效. 1. Bean Validation 中内置的 constraint 注解           ...

  5. 测试开发专题:如何在spring-boot中进行参数校验

    上文我们讨论了spring-boot如何去获取前端传递过来的参数,那传递过来总不能直接使用,需要对这些参数进行校验,符合程序的要求才会进行下一步的处理,所以本篇文章我们主要讨论spring-boot中 ...

  6. SpringBoot Validation参数校验 详解自定义注解规则和分组校验

    前言 Hibernate Validator 是 Bean Validation 的参考实现 .Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的 ...

  7. Java web服务端参数校验Javax.validation (springboot)

    一.基本使用 Javax.validation是spring集成自带的一个参数校验接口.可通过添加注解来设置校验条件. 下面以springboot项目为例进行说明.创建web项目后,不需要再添加其他的 ...

  8. 使用Spring Validation优雅地校验参数

    写得好的没我写得全,写得全的没我写得好 引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(Use ...

  9. SpringBoot - Bean validation 参数校验

    目录 前言 常见注解 参数校验的应用 依赖 简单的参数校验示例 级联校验 @Validated 与 @Valid 自定义校验注解 前言 后台开发中对参数的校验是不可缺少的一个环节,为了解决如何优雅的对 ...

  10. Java Bean Validation(参数校验) 最佳实践

    转载来自:http://www.cnblogs.com 参数校验是我们程序开发中必不可少的过程.用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的 ...

随机推荐

  1. 升级java11后,maven命令打包报错

    一.问题 升级java11后,maven命令打包报错: mvn clean package -Dmaven.test.skip=true [ERROR] Failed to execute goal ...

  2. 拯救“消失的她”——双系统grub完美恢复方案

    双系统grub意外消失怎么办? 不用重装系统.不用去维修店.不会丢数据,教你一招,完美恢复grub! 背景 我的电脑是windows和linux双系统,启动项使用的grub.某天准备切换linux时突 ...

  3. 使用docker搭建seafile服务器

    工作需要在单位和家里的不同电脑上同步指定文件夹及其内容.对比了一些解决方案,最终还是选择熟悉的seafile来做. 需要按照官方文档进行seafile的安装,选择官方推荐的docker方式快速部署. ...

  4. Vue源码学习(七):合并生命周期(混入Vue.Mixin)

    好家伙,   1.使用场景 现在来,来想一下,作为一个使用Vue的开发者,假设现在我们要使用created(),我们会如何使用 1.1.  .vue文件中使用 <template> < ...

  5. 西门子300PLC转以太网无需编程实现与1200PLC转以太网数据交换

    西门子300PLC转以太网无需编程实现与1200PLC转以太网数据通信 本文介绍利用兴达易控生产的PLC转以太网模块(MPI-ETH-XD1.0Plus)实现1200/1500PLC与300(CPU3 ...

  6. Mysql忘记密码后如何重置密码

    长时间不使用本机的Mysql后把密码忘记了咋整?直接上干货: 第一步(Mysql部署的位置,若自己能找到就忽略这一步):任务管理器中也可以找到 第二步:修改配置文件 在my.ini末尾加上 skip- ...

  7. 整理php防注入和XSS攻击通用过滤

    对网站发动XSS攻击的方式有很多种,仅仅使用php的一些内置过滤函数是对付不了的,即使你将filter_var,mysql_real_escape_string,htmlentities,htmlsp ...

  8. The method dismissDialog(int) from the type Activity is deprecated

    The method showDialog(int) from the type Activity is deprecated in android?   up vote6down votefavor ...

  9. junit4单元测试报错Invalid project specified

    junit4单元测试报错Invalid project specified. 前天在进行单元测试的时候出现了Invalid project specified的报错查了一下发现是项目名字的问题.项目名 ...

  10. P8741 [蓝桥杯 2021 省 B] 填空问题 题解

    P8741 [蓝桥杯 2021 省 B] 填空问题 题解 题目传送门 欢迎大家指出错误并联系这个蒟蒻 更新日志 2023-05-09 23:19 文章完成 2023-05-09 23:20 通过审核 ...