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

news2025/1/15 12:56:18

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,字符串是否符合邮件格式

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

package jakarta.validation.constraints;

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

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

2.2 自定义校验注解

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

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

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

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

/**
 * 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 是自定义的处理类:

public class IntRangeValidator implements ConstraintValidator<IntRange, Object> {

    private int min;

    private int max;

    @Override
    public 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;
        }
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        return isValid(value);
    }
}

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

在这里插入图片描述

2.3 自定义异常处理

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

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

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

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. 什么是类的定义设置了属性校验

    • 例如这个对象:

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

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

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

@Data
public class LoginDTO {

    @Valid
    private EmailLoginDTO emailLoginDTO;

    @Valid
    private WxLoginDTO wxLoginDTO;
}
2.4.3 特殊需求

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

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

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

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 对应的目标方法,例如一下示例:

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

@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.coloradmin.cn/o/2133188.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【原创】java+springboot+mysql高校社团网系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

spring内置的

程序里注入了spring内置的线程池&#xff0c;但没有看到线程池相关参数配置&#xff08;corePoolSize maxPoolSize 队列大小&#xff09;&#xff0c;网上查说默认是1个线程&#xff0c;结果和生产实际看到的不一致。 从生产可以看到有8个线程在跑&#xff0c;task-1 task-8&am…

buildroot移植qt报错Info: creating stash file (补充qt添加字库)

移植qt库&#xff0c;编译文件报错Info: creating stash file /home/rbing/QT/uart/.qmake.stash Project ERROR: Unknown module(s) in QT: serialport rbingouc:~/QT/uart$ /home/rbing/linux/tool/buildroot-2022.02.9/output/host/usr/bin/qmake Info: creating stash fil…

PCI Express 体系结构导读摘录(六)

系列文章目录 PCI Express 体系结构导读摘录&#xff08;一&#xff09; PCI Express 体系结构导读摘录&#xff08;二&#xff09; PCI Express 体系结构导读摘录&#xff08;三&#xff09; PCI Express 体系结构导读摘录&#xff08;四&#xff09; PCI Express 体系结构导读…

HarmonyOS开发实战( Beta5.0)画笔调色板案例实践

鸿蒙HarmonyOS开发往期必看&#xff1a; HarmonyOS NEXT应用开发性能实践总结 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门到精通&#xff09; 介绍 本示例实现了一个网格渐变的画笔调色板&#xff0c;能够根…

Vector - VT System - 板卡_VT板卡使用介绍_01

总体介绍 在常规的车载网络测试中&#xff0c;除了我们常用的使用VN系列设备进行总线协议测试&#xff0c;大多数公司都会将协议强相关的功能测试放在了功能侧&#xff0c;但是实际上这块对于车载网络测试工程师来说也是需要去了解的&#xff0c;毕竟只有懂协议的人才能更好的测…

Python with 关键字语法糖

参考文章: Python with 关键字 | 菜鸟教程 (runoob.com)https://www.runoob.com/python3/python-with.html Python 中的 with 语句用于异常处理&#xff0c;封装了 try…except…finally 编码范式&#xff0c;提高了易用性。 with 语句使代码更清晰、更具可读性&#xff0c; 它…

Fake Location模拟定位,刷跑 “运动世界校园”

前言:"科技改变生活&#xff0c;如果本文章对你有帮助&#xff0c;别忘记留下你的点赞&#xff0c;以下我对环境特变刁钻的运动世界校园为实例&#xff0c;也是成功安全正常上传数据&#xff0c;如果遇到问题&#xff0c;请留言评论区&#xff0c;所有链接我会放在文章头部…

157-安全开发-Python 自动化挖掘项目SRC 目标FOFA 资产Web 爬虫解析库

案例一&#xff1a;Python-WEB 爬虫库&数据解析库 这里开发的内容不做过多描述&#xff0c;贴上自己写的代码 爬取数据 要爬取p标签&#xff0c;利用Beautyfulsoup模块 import requests,time from bs4 import BeautifulSoup#url"https://src.sjtu.edu.cn/rank/firm…

99AutoML 自动化机器学习实践--NNI 自动化机器学习工具包

NNI 自动化机器学习工具包 NNI 是 Neural Network Intelligence 的缩写&#xff0c;可以译作&#xff1a;智能神经网络。名字听起来陌生&#xff0c;但 NNI 实际上就是一个自动化机器学习工具包。它通过多种调优的算法来搜索最好的神经网络结构和超参数&#xff0c;并支持单机、…

【Fastapi】使用Pandas作为大数据分析处理工具

【Fastapi】使用Pandas作为大数据分析处理工具 gitee https://gitee.com/zz1521145346/fastapi_frame.git github https://github.com/zz001357/fastapi_frame.git 准备工作 能联接的sql软件&#xff08;如&#xff0c;mysql&#xff09; 安装pandas &#xff08;pip in…

vue3 使用swiper制作带缩略图的轮播图

效果图 实现代码 <template><div class"wrap"><!-- 主轮播图 --><swiper :style"{--swiper-navigation-color: #fff,--swiper-pagination-color: #fff,}" :modules"modules" :navigation"true" :thumbs"{ …

深圳建站公司-如何做网站

深圳建站公司&#xff1a;如何制作一个成功的网站 在信息化快速发展的今天&#xff0c;企业和个人越来越重视网络形象&#xff0c;网站成为了展示品牌、推广产品和服务的重要平台。深圳作为科技创新和经济发展的前沿城市&#xff0c;涌现出许多专业的建站公司&#xff0c;能够为…

食品分类2检测系统源码分享

食品分类2检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vi…

【Leetcode:257. 二叉树的所有路径 + 二叉树 + 递归 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

多语言文本检测系统源码分享

多语言文本检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

中国水土保持能力防治数据集(1992-2019)

该数据集包括1992年至2019年中国每年的水土保持能力及其影响因子。这些数据是基于改进的RUSLE模型开发的&#xff0c;其中包含植被覆盖和管理(C)因子和降雨侵蚀率(R)因子作为重要的输入因子&#xff0c;针对不同区域进行了优化。 其中该数据集一共包含了9个数据它们分别是&…

【遍历二叉树】---先,中,后,层序遍历 及 先序建立整树

0.二叉树结点的链式存储结构 #include<stdio.h> #include<stdlib.h>typedef char TElemType;//树中元素基本类型为char类型#define bool int #define true 1 #define false 0//二叉树结点链式存储结构&#xff08;二叉链表&#xff09; typedef struct BiNode {TE…

java项目之基于springboot的贸易行业crm系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的基于springboot的贸易行业crm系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于sp…

GNSS多路径误差提取CMC和MPC

基本概念 伪距和载波相位观测值的多径误差并不相同&#xff0c;多径误差一般1-5米&#xff0c;最高可达10-20米。PPP利用伪距辅助模糊度固定&#xff0c;伪距质量不高多路径误差太大&#xff0c;会导致模糊度固定错。载波相位的多径误差小于四分之一波长。由于载波相位的多径误…