优雅的springboot参数校验(二)

news2024/12/26 22:58:32

7. 集合校验

        有这样一种场景,前端请求后端接口时,需要传递的是一个数组,数组的元素是一个对象,并且希望后台收到参数后可以对数组集合中的元素元素对象的属性进行校验,如果后台直接以List的来接收参数,约束注解的校验规则并不会触发,类似这样:

@PostMapping("/add")
public String add(@Validated(AddStuAndTeach.class) @RequestBody List<Teacher> teachers) {
    System.out.println("添加老师:" + teachers.size());
    return "success";
}
@Data
public class Teacher {
    @NotNull(message = "学生的老师姓名不能为空",groups = AddStuAndTeach.class)
    private String tecName;
    @NotNull(message = "教授科目不能为空",groups = AddStuAndTeach.class)
    private String subject;
}

    那么应该怎么办呢?

        1.需要重新实现List接口,并且在实现类里声明一个List类型变量,并且用@Valid声明;@Delegate是lombok的注解,其作用就是为变量生成一些常用方法,和@Data比较类似,具体可以自行检索lombok相关用法;(当然也可以不用这个注解,但是需要自行实现List接口的相关方法);

        2.集合内元素对象上的约束注解的用法和参数基础校验一样;

        3.controller层方法内要用重新实现List接口的类来接收前台传过来的参数,并且添加@Validated @RequestBody注解;

        总结:这种用法感觉有些奇怪,但是很有效,也算是解决了集合类参数校验的问题了。

@Data
public class ValidationList<E> implements List<E> {
    @Valid
    @Delegate
    private List<E> list = new ArrayList<>();
}
@Data
public class Teacher {
    @NotNull(message = "学生的老师姓名不能为空",groups = AddStuAndTeach.class)
    private String tecName;
    @NotNull(message = "教授科目不能为空",groups = AddStuAndTeach.class)
    private String subject;
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {
    @PostMapping("/add")
    public String add(@Validated(AddStuAndTeach.class) @RequestBody ValidationList<Teacher> teachers) {
        System.out.println("添加老师:" + teachers.size());
        return "success";
    }
}

8. 自定义校验

        上面分享了参数的基础校验以及一些特殊场景下的参数校验,比如嵌套校验、分组校验、集合校验,但是如果和业务相关的,需要查询数据库才能进行校验的业务参数校验是不是还得像开头说的,用大量的if进行啰嗦的判断,答案是否定的,java API除了定义了一些标准的用法,同是也对外暴露了校验验证器接口(ConstraintValidator),让用户自己实现一些自定义的校验逻辑。具体怎么用呢?下面让慢慢道来:

        1.需要声明一个自定义约束注解,如@SexValid(校验性别格式是否正确)、@StuCodeValid(校验学生是否重复),@NotNull是java API已经定义好的,可以参考一下看看人家是怎么定义的;

        2.实现校验验证器接口(ConstraintValidator),并且重写有效性校验逻辑;(这里需要特别注意一下,如果校验通过,返回true; 如果校验校验不通过就返回false,剩下抛出异常、捕获异常就不管了);

        3.把我自定义的好的约束注解应用到controller层方法参数对象的属性上;

        我用两个例子来说明一下

        第一个:假如在添加学生的时候需要校验学号是否已经分配给其他学生了

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Constraint(validatedBy = StuCodeValidator.class)
public @interface StuCodeValid {
    String[] value() default {};
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
@Component
public class StuCodeValidator implements ConstraintValidator<StuCodeValid,String> {
    @Autowired
    private StudentService studentService;
    /**
     * 参数有效性校验
     * @param value
     * @param context
     * 校验规则:
     * 如果学生学号发生重复为无效返回false;
     * 如果学生学号不重复会有效,则返回true;
     * @return
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value==null) {
            return false;
        }
        //查询学号是否重复,如果重复返回true,否则近回false;
        boolean flag = studentService.queryStuCodeRepeat(value);
        return !flag;
    }
}
@Data
public class Student implements Serializable {

    @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    private Integer id;
    @NotNull(message = "学号不能为空",groups = AddStudent.class)
    @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    @StuCodeValid(groups = AddStudent.class,message = "学生的学号不能重复")
    private String stuCode;
    @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    private String stuName;
}
@PostMapping("/add")
public Student add(@Validated(AddStudent.class) @RequestBody Student student) {
    System.out.println(student.getStuName());
    return student;
}

第二个:假如在添加学生的时候需要校验学生的性别必须是“男”或“女”

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Documented
@Constraint(validatedBy = SexValidator.class)
public @interface SexValid {
    //定义注解的里值
    String[] value() default {"男","女"};
    //定义异常信息
    String message() default "性别格式错误,请更正";
    //如果是需要分组校验,这个属性就用得上了
    Class<?>[] groups() default {};
    //这个可以携带无
    Class<? extends Payload>[] payload() default {};
}
public class SexValidator implements ConstraintValidator<SexValid,String> {

    private String[] values;
    @Override
    public void initialize(SexValid constraintAnnotation) {
        this.values=constraintAnnotation.value();
    }
    /**
     * 参数有效性校验
     * @param value
     * @param context
     * @return 如果参数有效,返回true;否则false
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        List<String> list = Arrays.asList(values);
        if (value==null) {
            return  false;
        }
        if (list.contains(value)) {
            return true;
        }
        return false;
    }
}
@Data
public class Student implements Serializable {

    @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    private Integer id;
    @NotNull(message = "学号不能为空",groups = AddStudent.class)
    @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    private String stuCode;
    @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    private String stuName;
    @SexValid(groups = AddStudent.class)
    private String sex;
}
@PostMapping("/add")
public Student add(@Validated(AddStudent.class) @RequestBody Student student) {
    System.out.println(student.getStuName());
    return student;
}

        总结:通过自己实现校验验证器,弥补了一些特殊场景下的校验需求,再也不用if esle了,代码复用性、可阅读性都大大提高了,整个controller看起来都无比清爽了。当然,方法虽妙,也要特别注意一下ConstraintValidator#isValid()方法的校验逻辑是:只有校验通过才返回true,false表示触发校验规则了,校验不通过,后面要抛出异常提示了。

        自定义校验除了通过注解这种声明式的实现外,还有一种编程式的实现,就好像spring的事务管理有两种:一种是声明式的,通过注解实现;另外一种是编程式,就硬编码来管理事务;事实上,如果非要硬编码,不如还用if else更简单直观,所以通常不管是事务管理、还是参数校验,建议还是用声明式的这种,比较优雅。

9. 快速失败

        通常情况下,Spring Validation默认为校验完所有的字段,然后才抛出异常;当然,如果你希望一旦校验失败就马上返回,不等校验完所有字段,那么就需要手动开启快速失败的模式:

@Configuration
public class ValidCofing {

    @Bean
    public Validator validator(AutowireCapableBeanFactory springFactory) {
        try (ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败
                .failFast(true)
                // 解决 SpringBoot 依赖注入问题
                .constraintValidatorFactory(new SpringConstraintValidatorFactory(springFactory))
                .buildValidatorFactory()) {
            return factory.getValidator();
        }
    }
}

总结

  1. Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。
  2. hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
  3. Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验
  4. @Validated是spring validation提供的能力,支持分组校验,不支持嵌套校验;
  5. @Valid是hibernate validation提供的能力,支持嵌套校验,不支持分组校验;
  6. 最后一点很坑,困惑我很久,@Validated:可以用在类型、方法和方法参数上,@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上,事实上是这样不?当然是否定的,从注解上看,确实是这样,但是如果没有对应的具体实现,可以也只是“可以”而已。

        以上hibernate-validator和spring validaton提供的关于参数校验的实战应用。

优雅的springboot参数校验(二)_凡夫贩夫的博客-CSDN博客有这样一种场景,前端请求后端接口时,需要传递的是一个数组,数组的元素是一个对象,并且希望后台收到参数后可以对数组集合中的元素元素对象的属性进行校验,如果后台直接以List的来接收参数,约束注解的校验规则并不会触发,类似这样: 那么应该怎么办呢? 1.需要重新实现List接口,并且在实现类里声明一个List类型变量,并且用@Valid声明;@Delegate是lombok的注解,其作用就是为变量生成一些常用方法,和@Data比较类似,具体可以自行检索lombok相关用法;(当然https://blog.csdn.net/fox9916/article/details/128177232?spm=1001.2014.3001.5502

优雅的springboot参数校验(一)_凡夫贩夫的博客-CSDN博客在后端的接口开发过程,实际上每一个接口都或多或少有不同规则的参数校验,有一些是基础校验,如非空校验、长度校验、大小校验、格式校验;也有一些校验是业务校验,如学号不能重重复、手机号不能重复注册等;对于业务校验,是需要和数据库交互才能知道校验结果;对于参数的基础校验,是有一些共有特征可以抽象出来,可以做成一个通用模板(java就是一种面向对象的编程语言,还记得天天快要说烂问烂的面向对象的三大特性吗?)。基于实际场景的需要,java API中定义了一些Bean校验的规范标准(JSR303:validation-ahttps://blog.csdn.net/fox9916/article/details/128167554?spm=1001.2014.3001.5502

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61212.html

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

相关文章

[附源码]计算机毕业设计基于springboot的云网盘设计

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

linux网络编程之tcp

相关函数 int socket(int domain,int type,int protocol);参数&#xff1a; domain&#xff1a; AF_INET AF_INET6 AF_UNIX,AF_LOCAL AF_NETLINK AF_PACKET type&#xff1a; SOCK_STREAM: 流式套接字&#xff0c;唯一对应于TCP SOCK_DGRAM:数据报套接字&#xff0c;唯一对应着…

【第七章 MySQL体系结构、存储引擎、InnoDB、MyISAM、Memory、存储引擎特点及选择】

第七章 MySQL体系结构、存储引擎、InnoDB、MyISAM、Memory、存储引擎特点及选择 1.MySQL体系结构&#xff1a; ①连接层&#xff1a; 最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连…

[Power Query] 日期和时间处理

Power Query查询编辑器为日期和时间数据提供了强大而快捷的处理方式 例1: 从日期中提取年、月份、日、季度、周、天等信息 数据源 步骤1:将数据源导入到Power BI Desktop&#xff0c;单击【转换数据】选项&#xff0c;进入Power Query查询编辑器界面 步骤2:选中"日期&qu…

【概念】数据仓库和数仓建模

数据仓库 数仓主要特征 面向主题&#xff1a;每个需求和表都属于一个主题&#xff0c;可以用主题来对数仓的表分门别类集成性&#xff1a;将异构数据源&#xff0c;比如MySQL和服务器埋点日志&#xff0c;统一转换成结构化的hive表数据存储到ODS层非易失性&#xff1a;对历史…

Flink系列文档-(YY12)-窗口计算

1 窗口基本概念 1.1 概述 窗口&#xff0c;就是把无界的数据流&#xff0c;依据一定规则划分成一段一段的有界数据流来计算&#xff1b; 既然划分成有界数据段&#xff0c;通常都是为了"聚合"&#xff1b; Keyedwindow重要特性&#xff1a;任何一个窗口&#xff0…

游戏开发36课 cocoscreator scrollview优化

在cocoscreator内&#xff0c;ScrollView控件封装的挺完美的了&#xff0c;不过对于一些对性能要求比较高的场景&#xff0c;会存在问题&#xff0c;以top100排行榜排行榜举例子 1、应用卡顿甚至崩溃 按照官方用例使用ScrollView&#xff0c;插入100个玩家的item&#xff0c;理…

离线安装harbor容器镜像仓库(harbor-v2.3.5)

记录&#xff1a;354 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;离线部署harbor容器镜像仓库集群&#xff0c;使用Redis为外部缓存、使用PostgreSQL为外部数据库、使用Ceph为共享存储、使用nginx为harbor的负载均衡、使用Keepalived为集群高可用、使用docker-ce操作…

【专业术语】(计算机 / 深度学习与目标检测 / 轨道交通)

专业术语-计算机 / 深度学习与目标检测 / 轨道交通一、 计算机1 IDE2 API3 CUDA Driver API3.1 cuInit - 驱动初始化3.2 关于context&#xff0c;有两种&#xff1a;3.3 CUcontext4 RuntimeAPI5 Memory5.1 关于内存&#xff0c;有两大类&#xff1a;5.1.1 CPU内存&#xff0c;称…

[附源码]Python计算机毕业设计Django考试系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

FISCO BCOS(二十五)———多机部署

一、基础环境搭建 1、查看当前是否安装了ssh-server服务 dpkg -l | grep ssh2、安装ssh-server服务 sudo apt-get install openssh-server3、修改配置文件"/etc/ssh/sshd_config" 4、重启openssh-server root@FISCOBCOS01:~# sudo /etc/init.d/ssh restart5、查看…

高数 |【2020数一真题】部分错题及经典题自用思路整理

T1:积分限与被积函数都等价为无穷小 T2:不连续一定不可导 T3:可微的定义 T4:收敛半径 T6:空间直线与向量 法一:

WINDOWS7-11磁盘分区教程

首先&#xff0c;打开计算机管理界面 以windows11为例&#xff0c;在任务栏搜索框内&#xff0c;输入“计算机管理界面” 点击打开。 然后选择存储-》磁盘管理 到这里之后&#xff0c;我们需要选中一个磁盘&#xff0c;压缩卷分出来一部分空间。为新分区做准备。 以E盘为例&a…

MapStruct与lombok加载顺序问题与annotationProcessorPaths的关系?

MapStruct是什么&#xff1f; MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.——https://mapstruct.org/ 从官方定义来看&#xff0c;MapStruct类似于我…

C语言Socket编程,实现两个程序间的通信

文章目录server和client通信流程图实现两个程序间的通信1.服务端server2.客户端client3.怎么运行呢&#xff1f;4.重写代码已剪辑自: https://www.cnblogs.com/fisherss/p/12085123.html server和client通信流程图 在mooc上找到的,使用Socket客户端client和服务端server通信的…

17-JavaSE基础巩固练习:Math类API两道数学算法水题

两道算法水题&#xff08;一&#xff09; 一、判断质数 判断一个数是否为一个质数。 1、以前的写法 package com.app.demo26_math_api;public class Test1 {public static void main(String[] args) {/*判断n(任意整数)是否为一个质数&#xff1a;以前的写法:是用n对2~n之间…

JavaWeb Filter 过滤器

参考&#xff1a;JavaWeb过滤器(Filter)详解 1、简介 顾名思义就是对事物进行过滤的&#xff0c;在Web中的过滤器&#xff0c;当然就是对请求进行过滤&#xff0c;我们使用过滤器&#xff0c;就可以对请求进行拦截&#xff0c;然后做相应的处理&#xff0c;实现许多特殊功能。…

阿里云部署应用

安装jdk 查看已安装版本 rpm -qa | grep java yum命令查找JDK1.8软件包 yum -y list java-1.8* 安装列表中的JDK1.8软件包 yum -y install java-1.8.0-openjdk-devel.x86_64 java -version 配置环境变量 vim /etc/profile JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk-1.8.…

三、内存管理 (二)虚拟存储器

目录 2.1虚拟内存的基本概念 2.2内存分配策略 2.2.1驻留集大小 2.2.2固定分配局部置换 2.2.3可变分配全局置换 2.2.4可变分配局部置换 2.3地址变换机构 2.3.1页表机制 2.3.2预调页策略和请求调页策略 2.3.3缺页中断机构 2.3.4对换区与文件区 2.3.5页面置换算法 …

搭建springWeb保姆级教程

经过我们对mybatis和spring框架的学习&#xff0c;我们即将要用框架进行前后端数据交互&#xff0c;已经脱离了那种用servlet的方式进行数据传输&#xff0c;今天让我们来搭建最基本的springweb框架&#xff01;&#xff01;&#xff01; 1.创建一个web项目 1. 2. 选择一个we…