【Java Bean Validation API】Spring3 集成 Bean 参数校验框架

embedded/2024/9/23 9:25:37/

Spring3 集成 Bean 参数校验框架 Java Bean Validation API

1. 依赖

Spring 版本:3.0.5

Java 版本:jdk21

检验框架依赖(也可能不需要,在前面 spring 的启动依赖里就有):

<!-- 自定义验证注解 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

目前我还没有找到 spring/java 低版本的一个很方便的方式去进行参数校验,之前的 javax 无法实现,在高版本中被更名为 jakarta,我们使用的就是其中的 jakarta

2. 基本使用

2.1 常见注解

AnnotationDescription
@NotNull参数不能为 null
@NotBlank参数不能为 null 或 trim 后为空字符串
@NotEmpty字符串、数组、集合是否为 null 也不为空字符串、空数组、空集合
@Size若不为 null,指定字符串、数组、集合的长度范围
@Max若不为 null,参数最大值
@Min若不为 null,参数最小值
@DecimalMax若不为 null,采取精度较高的最小值限制
@DecimalMax若不为 null,采取精度较高的最大值限制
@Pattern若不为 null,字符串正则表达式匹配
@Email若不为 null,字符串是否符合邮件格式

按需去查就行,更多注解在:

java">package jakarta.validation.constraints;

自觉规范地据场景去使用,注解可以标注在任何地方,但是不是每个地方都有用,轻则失效,重则报错

精力有限,这些我们也没法一一去探寻“乱搞的现象”

2.2 自定义校验注解

如果框架自带的不足以满足我们的要求,那么我们可以选择自定义注解

例如,这些注解都无法针对 Map 这种非单列的类型

或者,我们需要一个注解,其可以检测一个 Number 类型的或者其数组集合的对象,若不为 null,元素在一个特定的数值范围内

我们就要自己去写一个会被 Jakarta 框架识别的注解:

java">/*** Created With Intellij IDEA* User: 马拉圈* Date: 2024-08-07* Time: 17:19* Description: 此注解用于判断数值是否在规定氛围内* min 代表最小值,max 代表最大值,被注解的变量数值必须在闭区间 [min, max]* 支持该变量是 Number 类型的变量,以及其数组、集合;* 对于数组和集合,必须每个元素都满足该规则,否则就不通过*/
@Documented
@Constraint(validatedBy = {IntRangeValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {String message() default "数值不在有效范围内"; // 默认消息int min();int max();Class<?>[] groups() default {}; // 分组校验Class<? extends Payload>[] payload() default {}; // 负载信息
}

在这里插入图片描述

黄色为必须部分,红色为自定义部分,其中 IntRangeValidator.class 是自定义的处理类:

java">public class IntRangeValidator implements ConstraintValidator<IntRange, Object> {private int min;private int max;@Overridepublic void initialize(IntRange intRange) {this.min = intRange.min();this.max = intRange.max();}private int compare(Number number1, Number number2) {return Double.compare(number1.doubleValue(), number2.doubleValue());}private boolean isValid(Object value) {if(Objects.isNull(value)) {return Boolean.TRUE;} else if (value instanceof Number number) {return compare(number, min) >= 0 && compare(number, max) <= 0;} else if (value instanceof Collection<?> collection) {return collection.stream().allMatch(this::isValid);} else if (value.getClass().isArray()) {int length = Array.getLength(value);for (int i = 0; i < length; i++) {if(!isValid(Array.get(value, i))) {return Boolean.FALSE;}}return Boolean.TRUE;} else {return Boolean.FALSE;}}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {return isValid(value);}
}

代码就不解释了,主要是得实现 ConstraintValidator 接口,其中第一个泛型是自定义注解类,第二个泛型是预期注解标注在什么类型的对象上,isValid 返回 false,就拦截

在这里插入图片描述

2.3 自定义异常处理

如果拦截,会统一抛出异常:MethodArgumentNotValidException.class 或者 ConstraintViolationException.class

  • MethodArgumentNotValidException 由一整个对象被检测出问题时抛出
  • ConstraintViolationException 由单一属性或单一参数被检测出问题时抛出
  • 可能有其他,但是如果是违背我们的注解那一定是上面这两个,其他可能是使用不当的问题

我觉得都处理就行,不要纠结抛哪个异常,都处理就行:

java">public static SystemJsonResponse getGlobalServiceExceptionResult(GlobalServiceException e, HttpServletRequest request) {String requestURI = request.getRequestURI();String message = e.getMessage();GlobalServiceStatusCode statusCode = e.getStatusCode();log.error("请求地址'{}', {}: {}", requestURI, statusCode, message);return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(statusCode, message);
}
/*** 自定义验证异常*/
@ExceptionHandler(ConstraintViolationException.class)
public SystemJsonResponse constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {log.error("数据校验出现问题,异常类型:{}", e.getMessage());String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).filter(Objects::nonNull).collect(Collectors.joining("\n"));return getGlobalServiceExceptionResult(new GlobalServiceException(message, GlobalServiceStatusCode.PARAM_FAILED_VALIDATE),request);
}@ExceptionHandler(MethodArgumentNotValidException.class)
public SystemJsonResponse ValidationHandler(MethodArgumentNotValidException e, HttpServletRequest request) {log.error("数据校验出现问题,异常类型:{}", e.getMessage());String message = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).filter(Objects::nonNull).collect(Collectors.joining("\n"));return getGlobalServiceExceptionResult(new GlobalServiceException(message, GlobalServiceStatusCode.PARAM_FAILED_VALIDATE),request);
}

2.4 如何应用

2.4.1 触发条件

最重要的条件是:

  1. 必须是 Bean 对象的实例方法
  2. 检测的对象是方法的形参
2.4.1 形参是普通类型

我们使用 @NotNull 等检测参数的注解,或者自定义的校验注解,标注在形参之前,注解的校验可以进行叠加

并且,我们需要在 Bean 的类之前标注 @Validated,声明这个 Bean 的方法参数受代理

这个 bean 在调用这个方法的时候,输入参数就会被监控

2.4.2 形参是自定义类型

我们使用 @NotNull 等检测参数的注解,或者自定义的校验注解,标注在形参之前,注解的校验可以进行叠加

如果这个类的定义设置了属性校验,我们要对其内部每个属性都校验,那就标注 @Valid,表示循环递归校验

其中,@Valid 的触发不依赖于类上的 @Validated,其他注解则依赖

  1. 什么是循环递归校验

    • 如果 @Valid 标注的是集合或数组,则依次对每个元素校验,递归就是校验元素内部的属性
  2. 什么是类的定义设置了属性校验

    • 例如这个对象:

    • java">@Data
      public class EmailLoginDTO {@NotBlank(message = "code 不能为空")private String code;@NotBlank(message = "邮箱不能为空")@Email(message = "邮箱格式不合法")private String email;
      }
      

值得注意的是,@Valid 只会在非 null 的时候触发

若这个对象,的属性又有自定义对象,则继续标注 @Valid 循环递归校验即可,

java">@Data
public class LoginDTO {@Validprivate EmailLoginDTO emailLoginDTO;@Validprivate WxLoginDTO wxLoginDTO;
}
2.4.3 特殊需求

如果你需要对一个方法的返回值进行校验,如果直接标注注解在返回值类型前,是无效的;

  1. 对于普通类型,我们手动校验没啥大问题
  2. 对于自定义类型,且类的定义设置了属性校验,我们可不想再写一遍啊~

我们其实可以通过封装以下这个方法进行校验:

java">import cn.hutool.extra.spring.SpringUtil;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;import java.util.Set;public class ValidatorUtils {private static final Validator validator = SpringUtil.getBean(Validator.class);public static <T> void validate(T object, Class<?>... groups) {Set<ConstraintViolation<T>> validate = validator.validate(object, groups);if (!validate.isEmpty()) {String message = String.format("请求对象:'%s'", object.toString());throw new ConstraintViolationException(message, validate);}}
}

我们只需要将方法的返回值传进去就行(groups 可以为空数组),便可完成对自定义类的校验

2.4.4 主要应用场景

主要应用场景就是用于 Controller 的目标方法,因为 Controller 也是 Bean 嘛,接受请求的时候,会调用这个 Bean 对应的目标方法,例如一下示例:

对于无状态的参数进行校验,与业务控制层解耦,避免了重复校验的冗余现象,也不会犯是在控制层还是在业务层进行校验的选择困难症

java">@RestController
@RequiredArgsConstructor
@Validated
public class XXXController {@PostMapping("/set/{value}")@Operation(summary = "设置值")public SystemJsonResponse setValue(@Valid @RequestBody XXXDTO xxxDTO,@NotBlank @RequestHeader("token") String token@IntRange (min = 1, max = 7) @PathVariable("value") Integer value) {// ......}}

更多使用场景,只要合理推理就应该没问题,举一反三一下就行,更多细节需要就去查去探索,这里就不一一罗列了


http://www.ppmy.cn/embedded/113459.html

相关文章

Excel数据转置|Excel数据旋转90°

Excel数据转置|Excel数据旋转90 将需要转置的数据复制在旁边空格处点击鼠标右键&#xff0c;选择图中转置按钮&#xff0c;即可完成数据的转置。&#xff01;&#xff01;&#xff01;&#xff01;非常有用啊啊啊&#xff01;&#xff01;&#xff01;

846. 树的重心

846. 树的重心 给定一颗树&#xff0c;树中包含 n个结点&#xff08;编号 1∼n&#xff09;和 n−1条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义&#xff1a;重心是指树中的一个结点&#xff0c;如果将这个…

大厂校招:唯品会Java面试题及参考答案

SortedSet 的原理 SortedSet 是一个有序的集合接口,它继承自 Set 接口。在 Java 中,常见的实现类有 TreeSet。 TreeSet 实现了 SortedSet 接口,它使用红黑树来维护集合中元素的有序性。红黑树是一种自平衡的二叉搜索树,具有以下特点: 每个节点要么是红色,要么是黑色。根节…

GPU加速生物信息分析的尝试

GPU工具分类 实话实说&#xff0c;暂时只有英伟达的GPU才能实现比较方便的基因组分析集成化解决方案&#xff0c;其他卡还需要努力呀&#xff0c;或者需要商业公司或学术团体的努力开发呀&#xff01;FPGA等这种专用卡的解决方案也是有的&#xff0c;比如某测序仪厂家&#xf…

除氟剂的介绍及用途

除氟剂是一种广泛应用于水处理和环保领域的重要化学品&#xff0c;其主要功能是通过与水体中的氟离子发生化学反应&#xff0c;将其转化为无害的物质&#xff0c;从而去除水中的氟含量。以下是对除氟剂的详细介绍和用途分析&#xff1a; 一、除氟剂介绍 种类与形态&#xff1a…

彩漩科技亮相企业出海峰会,展示智能办公新力量

近日&#xff0c;在北京市海淀区商务局的指导下&#xff0c;由中关村东升科技园联合创新企业科普联盟共同举办的企业出海峰会于北京成功举办。本次峰会以“出海新征程&#xff0c;企业新高度”为核心议题&#xff0c;深入探讨全球化背景下科技企业出海面临的机遇与挑战。通过汇…

网络安全学习(一)初识kali

kali是一个操作系统&#xff0c;和我们平时用的windows系统类似&#xff0c;只是kali是一个集合了很多工具的专用操作系统。 其官网是https://www.kali.org 点击download&#xff0c;选择安装到虚拟机 因为要安装在虚拟机上&#xff0c;所以我们先要安装VM。 使用下载好的kali…

2025 年考研数学二大纲原文(完整版)

2025 年考研数学二大纲原文(完整版) 考试科目&#xff1a;高等数学、线性代数 考试形式和试卷结构 一、试卷满分及考试时间 试卷满分为 150 分&#xff0c;考试时间为 180 分钟. 二、答题方式 答题方式为闭卷、笔试. 三、试卷内容结构 高等教学 约 80% 线性代数 约 20…