Spring中的数据校验

在这里插入图片描述

文章目录

  • 引言
  • 摘要
  • 正文
    • 基于 `ValidationUtils`的简单校验
    • 基于自定义 `Validator`的校验
    • Spring内置校验 `LocalValidatorFactoryBean`
    • `HibernateValidator`校验
      • 使用HibernateValidator自定义校验规则
  • 总结

引言

我们在日常的软件开发过程中,尤其是WEB开发过程中,数据校验功能是一个很常见的需求,最近在工作中偶然看到一些人还在使用简单的 if-elseif-else逻辑实现数据校验功能。实际上,在Spring框架下,或者说在JavaEE规范下,对数据校验工作已经有了最佳实践,对于简单校验工作可以通过Spring自带的数据校验工具类进行,对于复杂数据校验,更多的使用Hibernate-Validator框架进行实现,在这篇文章中,我们将对数据校验功能的方式做个总结。😶‍🌫️

摘要

本文主要介绍在Spring框架中如何进行数据校验工作,文章介绍了Spring自带的数据校验和基于Hibernate-Validator框架的数据校验,这些功能主要涉及 ValidationUtilsValidatorLocalValidatorFactoryBeanValidatorFactory等几个类。除此之外,还会介绍在SpringMvc框架下对网络请求数据的校验将如何实现,自定义校验规则将如何实现等内容

关键字:Spring,SpringMVC,Hibernate,Validator,校验,网络请求,注解,框架

正文

Spring框架提供了数据校验相关的一系列功能,数据校验功能围绕 org.springframework.validation.Errorsorg.springframework.validation.Validator展开,其中 Errors接口用于保存被校验对象的数据绑定和验证错误信息,Validator接口用于执行数据校验工作。而Spring框架提供了多种校验方式,下面我们一一介绍。

:Spring数据校验相关功能的API都在 org.springframework.validation这个包下,本文对于该包下的类只以类名显示

基于 ValidationUtils的简单校验

ValidationUtils类是Spring提供的一个数据校验工具类,主要提供空值校验和空值或空格校验,也可以通过传入 Validator实现功能更加丰富的数据校验工作。

下列代码用于校验一个Map中的 name 属性的值是否为空或只有空格

@Component
public class ValidatorComponent implements ApplicationRunner {    @Overridepublic void run(ApplicationArguments args) throws Exception {this.testMapValidate();}private void testMapValidate() {Map<String,Object> map=new HashMap<>(8);map.put("name"," ");map.put("gender", Gender.MALE);map.put("employ","wu");map.put("age",30);MapBindingResult mapError = new MapBindingResult(map,"zhouyu");ValidationUtils.rejectIfEmpty(mapError,"age","age is not null","age不能为空");ValidationUtils.rejectIfEmptyOrWhitespace(mapError,"name","name is not empty","name不能为空");for (ObjectError error : mapError.getAllErrors()) {String msg = error.getDefaultMessage();System.out.println("默认消息:"+msg);}}
}

执行这段程序的结果为

默认消息:name不能为空

除了设置默认提示消息之外,还可以通过SpringMessage模块设置自定义的提示消息,关于 SpringMessage的内容不属于本文的讨论范围,可以参考这篇文章,添加本地消息后的代码

//配置本地消息
@Configuration
public class Config{@Beanpublic MessageSource validatorMessageSource(){StaticMessageSource source = new StaticMessageSource();source.addMessage("age is not null", Locale.getDefault(),"年龄不能为空");source.addMessage("name is not empty",Locale.getDefault(),"姓名不能为空");source.addMessage("person is minor",Locale.getDefault(),"禁止雇佣未成年人");source.addMessage("this is female toilet",Locale.getDefault(),"男士止步");return source;}
}@Component
public class ValidatorComponent implements ApplicationRunner {@Resource(name = "validatorMessageSource")private MessageSource messageSource;@Overridepublic void run(ApplicationArguments args) throws Exception {PersonEntity person = new PersonEntity();}private void testMapValidate() {Map<String,Object> map=new HashMap<>(8);map.put("name","zhouyu");map.put("gender", Gender.MALE);map.put("employ","wu");map.put("age",30);MapBindingResult mapError = new MapBindingResult(map,"zhouyu");ValidationUtils.rejectIfEmpty(mapError,"age","age is not null","age不能为空");ValidationUtils.rejectIfEmptyOrWhitespace(mapError,"name","name is not empty","name不能为空");for (ObjectError error : mapError.getAllErrors()) {String msg = error.getDefaultMessage();System.out.println("默认消息:"+msg);//读取本地消息String code = error.getCode();Object[] argArr = error.getArguments();String message = this.messageSource.getMessage(code, argArr, Locale.getDefault());System.out.println("本地消息:"+message);}}
}

打印结果

默认消息:age不能为空
本地消息:年龄不能为空

除了对应Map类型校验结果的 MapBindingResult类,Spring中还提供了其他几种结果类,包括 BeanPropertyBindingResultDirectFieldBindingResult,两者皆可用于POJO对象的校验。BeanPropertyBindingResult的测试代码如下

private void testBeanValidate() {PersonEntity person = PersonEntity.bornFemale("daqiao");BeanPropertyBindingResult beanError = new BeanPropertyBindingResult(person, "person");beanError.rejectValue("name","name is not empty");for (ObjectError error : beanError.getAllErrors()) {String msg = this.messageSource.getMessage(error.getCode(),error.getArguments(),Locale.getDefault());System.out.println(msg);}
}

执行结果如下

姓名不能为空

基于自定义 Validator的校验

以上API只能实现简单的非空和空格校验,如果需要实现更加丰富的校验功能,可以通过实现 Validator接口自定义校验逻辑,一个简单的校验功能如下

@Bean
public Validator validator(){return new Validator() {@Overridepublic boolean supports(Class<?> clazz) {return PersonEntity.class.equals(clazz);}//实际的校验逻辑代码@Overridepublic void validate(Object target, Errors errors) {if (target instanceof PersonEntity){PersonEntity person = (PersonEntity) target;if(person.getAge()<18){errors.reject("person is minor");}if (Gender.MALE.equals(person.getGender())){errors.rejectValue("gender","this is female toilet");}}}};
}

测试自定义校验功能

@Component
public class ValidatorComponent implements ApplicationRunner {//自定义validator@Resourceprivate Validator validator;//自定义消息@Resource(name = "validatorMessageSource")private MessageSource messageSource;@Overridepublic void run(ApplicationArguments args) throws Exception {this.customValidate();}private void customValidate() {PersonEntity person = PersonEntity.bornMale("tb");//确认validator是否支持这个对象的校验if(this.validator.supports(person.getClass())){BeanPropertyBindingResult beanBindResult = new BeanPropertyBindingResult(person, "tb");//执行校验this.validator.validate(person, beanBindResult);for (ObjectError error : beanBindResult.getAllErrors()) {String msg = this.messageSource.getMessage(error.getCode(), error.getArguments(), Locale.getDefault());System.out.println(msg);}}}
}

这段代码的执行结果为

禁止雇佣未成年人
男士止步

除了直接调用 Validator类的方法,还可以通过 ValidatiionUtils工具类,传入 Validator实现校验,这样上例中的 customValidate()方法可以通过如下方式优化

private void customValidate() {PersonEntity person = PersonEntity.bornMale("tb");BeanPropertyBindingResult beanBindResult = new BeanPropertyBindingResult(person, "tb");ValidationUtils.invokeValidator(validator,person,beanBindResult);//打印校验结果beanBindResult.getAllErrors().forEach(o-> System.out.println(this.messageSource.getMessage(o.getCode(),o.getArguments(),Locale.getDefault())));
}

Spring内置校验 LocalValidatorFactoryBean

如果觉得自定义 Validator接口比较麻烦,可以尝试使用Spring提供的 LocalValidatorFactoryBean,这个类实现了 JSR-303(javax.validation)系列注解的支持。为方便测试,我们先修改 PersonEntity类,添加JSR-303校验注解

public class PersonEntity {@NotEmpty(message = "【person.name】不能为空")private String name;@Min(value = 1,message = "【person.age】不能小于1")private int age;private Gender gender;public static PersonEntity bornMale(String name){PersonEntity res = new PersonEntity();res.setGender(Gender.MALE);res.setAge(1);res.setName(name);return res;}public static PersonEntity bornFemale(String name){PersonEntity res = new PersonEntity();res.setGender(Gender.FEMALE);res.setAge(1);res.setName(name);return res;}@Overridepublic String toString() {final StringBuffer sb = new StringBuffer("PersonEntity{");sb.append("name='").append(name).append('\'');sb.append(", age=").append(age);sb.append(", gender=").append(gender);sb.append('}');return sb.toString();}
}

配置 Validator

@Bean
public Validator localValidator(){return new LocalValidatorFactoryBean();
}

编辑测试类

@Component
public class ValidatorComponent implements ApplicationRunner {//依赖注入LocalValidatorFactoryBean@Resourceprivate Validator validator;@Overridepublic void run(ApplicationArguments args) throws Exception {this.localBeanValidator();}private void localBeanValidator() {PersonEntity person = new PersonEntity();person.setAge(1);person.setGender(Gender.FEMALE);BeanPropertyBindingResult personResult = new BeanPropertyBindingResult(person, "person");this.validator.validate(person,personResult);personResult.getAllErrors().forEach(o-> System.out.println(o.getDefaultMessage()));}
}

输出结果

【person.name】不能为空

HibernateValidator校验

相比于Spring提供的 LocalValidatorFactoryBeanHibernateValidator是更常用的校验框架,它提供了更加丰富的功能,以及更简单的使用方式,HibernateValidator同样也支持 JSR-303,同时它也是SpringBoot(spring-boot-starter-validation)中默认提供的校验框架

HibernateValidator可以直接使用,Person类还是上例中的代码

@Override
public void run(ApplicationArguments args) throws Exception {this.hibernateValidate();
}private static void hibernateValidate() {PersonEntity person = PersonEntity.bornFemale("");person.setAge(-1);ValidatorFactory factory = Validation.buildDefaultValidatorFactory();javax.validation.Validator valid = factory.getValidator();Set<ConstraintViolation<PersonEntity>> res = valid.validate(person);res.forEach(o-> System.out.println(o.getMessage()));
}

校验结果如下

【person.name】不能为空
【person.age】不能小于1

除了主动使用 Hibernate-Validator进行校验,更加常用的一种方式是集成到SpringMVC中,对Web请求发来的参数进行校验,只需要引入响应的依赖包即可实现数据校验功能

<!--spring版本和hibernate-validator版本要一一对应,在本例中使用这两个版本-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.15.RELEASE</version>
</dependency>  <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.1.5.Final</version>
</dependency>

使用 @Validated注解校验

@Controller
@RequestMapping("/test")
public class TestDemoController {@PostMapping("/person")@ResponseBodypublic PersonEntity testPerson(@Validated @RequestBody PersonEntity person){return person;}
}

配置好项目后,启动tomcat服务器,发送如下请求测试

###
POST http://localhost:8080/mybatis_spring/test/person
Content-Type: application/json{
"age":12,"gender":"MALE"
}

发送请求后发现,服务其报错

05-Jan-2024 14:06:17.511 警告 [http-nio-8080-exec-3] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public top.sunyog.demo.mybatisspring.entity.PersonEntity top.sunyog.demo.mybatisspring.controller.TestDemoController.testPerson(top.sunyog.demo.mybatisspring.entity.PersonEntity): [Field error in object 'personEntity' on field 'name': rejected value [null]; codes [NotEmpty.personEntity.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [personEntity.name,name]; arguments []; default message [name]]; default message [【person.name】不能为空]] ]

这时说明Hibernate-Validator已经开始参与校验工作了,为了更好的服务体验,可以捕获这个异常,然后返回给服务端正常的提示语,下面列出示例代码

@ControllerAdvice
public class ErrorHandlerController {@ExceptionHandler(value = MethodArgumentNotValidException.class)@ResponseBodypublic String validError(MethodArgumentNotValidException e){BindingResult result = e.getBindingResult();StringBuffer buffer = new StringBuffer("校验错误:");result.getAllErrors().forEach(o->buffer.append(o.getDefaultMessage()).append("; "));return buffer.toString();}
}

这时请求返回值变成了

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 83
Date: Fri, 05 Jan 2024 06:46:32 GMT
Keep-Alive: timeout=60
Connection: keep-alive数据校验错误:【person.name】不能为空; 

使用HibernateValidator自定义校验规则

在HibernateValidator中,除了可以使用JSR-303中规定的校验注解之外,还支持自定义注解的校验,下面以一个校验受雇人是否在合法用工年龄的功能简单介绍自定义规则校验

  1. 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.TYPE,ElementType.FIELD})
@Documented
//标注具体的校验类
@Constraint(validatedBy = ChildConstraint.class)
public @interface WorkingAge {String message() default "年龄超过许可范围";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default {};
}
  1. 继承 ConstraintValidator接口
@Component
public class ChildConstraint implements ConstraintValidator<WorkingAge, Integer> {@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {//年龄允许在18-60之间return value >= 18 && value <= 60;}
}
  1. 标注自定义注解
public class PersonEntity {@NotEmpty(message = "【person.name】不能为空")private String name;@Min(value = 1,message = "【person.age】不能小于1")@WorkingAgeprivate int age;private Gender gender;
}
  1. 请求校验
@PostMapping("/valid")
public PersonEntity validPerson(@RequestBody @Validated PersonEntity person){return person;
}
POST http://localhost:18080/test/valid
Content-Type: application/json{"name": "sunshangxiang","age": 15,"gender": "FEMALE"
}

校验结果如下

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Fri, 05 Jan 2024 07:42:06 GMT
Keep-Alive: timeout=60
Connection: keep-alive数据校验错误:年龄超过许可范围; 

总结

在这篇文章中我们介绍了Spring框架中进行数据校验的方法,从简单到复杂依次介绍了

  1. ValidationUtils
  2. Validator
  3. LocalValidatorFactoryBean
  4. Hibernate-Validator

四种校验方式,其中前三种都是在 spring-context中提供的类,第四种使用的是开发过程中非常常用的 hibernate.validator依赖包,这个包提供了丰富的数据校验功能和扩展功能,基本满足各种各样的数据校验业务需求。


📩 联系方式
邮箱: qijilaoli@foxmail.com
掘金: 我的掘金
CSDN: 我的CSDN

❗版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问我的博客首页

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

antd Table 动态数据 合并单元格(合并行)

antd Table 组件动态合并单元格 最近处理table的时候 遇到了要合并同一列的几行的情况&#xff0c;比如第一列的前面三行都是同一个对象的名字&#xff0c;此时合并显示比较妥当&#xff0c;但是数据是后端接口来的&#xff0c;而且可以筛选条件&#xff0c;搜索出来的数据就是…

Linux的ping命令、wget命令、curl命令

一、ping命令 通过ping命令&#xff0c;可以检查指定的网络服务器是否是可联通状态 形式&#xff1a;ping [-c num] ip或主机名 -c&#xff1a;检查的次数&#xff0c;不使用-c&#xff0c;将无限次数持续检查 ip或主机名&#xff1a;被检查的服务器的ip地址或主机名地址 …

Jenkins分布式实现: 构建弹性和可扩展的CI/CD环境!

Jenkins是一个流行的开源持续集成&#xff08;Continuous Integration&#xff0c;CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;CD&#xff09;工具&#xff0c;它通过自动化构建、测试和部署过程&#xff0c;帮助开发团队更高效地交付软件。Jenkins的…

AtomicReference自旋加CAS保证对象引用原子性

AtomicReference类提供了对象引用的非阻塞原子性读写操作&#xff0c;并且提供了其他一些高级的用法&#xff0c;对象的引用其实是一个4字节的数字&#xff0c;代表着在JVM堆内存中的引用地址&#xff0c;对一个4字节数字的读取操作和写入操作本身就是原子性的 package Atomic…

数据结构入门到入土——ArrayList与顺序表

目录 一&#xff0c;线性表 二&#xff0c;顺序表 1.接口实现 三&#xff0c;ArrayList简介 四&#xff0c;ArrayList使用 1.ArrayList的构造 2.ArrayList常见操作 3.ArrayList的遍历 4.ArrayList的扩容机制 五&#xff0c;ArrayLisit的具体使用 杨辉三角 一&#x…

【SpringMVC】常用注解

什么是MVC&#xff1f; MVC是一种程序分层开发模式&#xff0c;分别是Model&#xff08;模型&#xff09;&#xff0c;View&#xff08;视图&#xff09;以及Controller&#xff08;控制器&#xff09;。这样做可以将程序的用户界面和业务逻辑分离&#xff0c;使得代码具有良好…

虚拟机快照

1.为什么使用快照 在学习阶段我们无法避免的可能损坏Linux操作系统。 如果损坏的话&#xff0c;重新安装一个Linux操作系统就会十分麻烦。 VMware虚拟机&#xff08;Workstation和Funsion&#xff09;支持为虚拟机制作快照。 通过快照将当前虚拟机的状态保存下来&#xff0c;在…

CentOS 7.6下的HTTP隧道代理配置详解

在CentOS 7.6操作系统中&#xff0c;配置HTTP隧道代理需要一定的技术知识和经验。下面我们将详细介绍如何配置HTTP隧道代理&#xff0c;以确保网络通信的安全性和稳定性。 首先&#xff0c;我们需要了解HTTP隧道代理的基本原理。HTTP隧道代理是一种通过HTTP协议传输其他协议数…

SSM农产品朔源管理系统----计算机毕业设计

项目介绍 本项目分为前后台&#xff0c;分为普通用户、管理员、企业用户三种角色&#xff1b; 普通用户无需登录&#xff0c;可在前台直接进行溯源查询&#xff0c;管理员、企业用户可登录后台进行管理&#xff1b; 超级管理员角色包含以下功能&#xff1a; 登录,管理企业,设…

用友U8 Cloud smartweb2.RPC.d XXE漏洞复现

0x01 产品简介 用友U8 Cloud 提供企业级云ERP整体解决方案,全面支持多组织业务协同,实现企业互联网资源连接。 U8 Cloud 亦是亚太地区成长型企业最广泛采用的云解决方案。 0x02 漏洞概述 用友U8 Cloud smartweb2.RPC.d接口处存在 XXE漏洞,攻击者可通过该漏洞获取敏感文件…

云卷云舒:【实战篇】Redis迁移

1. 简介 Remote Dictionary Server(Redis)是一个由Salvatore Sanfilippo写的key-value存储系统&#xff0c;是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2. 迁移原理 redis-sh…

性能优化-OpenMP基础教程(三)

本文主要介绍OpenMP并行编程的环境变量和实战、主要对比理解嵌套并行的效果。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &…

麒麟镜像下载

试用版下载链接 产品试用申请国产操作系统、银河麒麟、中标麒麟、开放麒麟、星光麒麟——麒麟软件官方网站 下载自己对应的操作系统 我下载的是 共享文件下载 - Kylin Distro 然后用迅雷下载就可以了

基于sumo实现交通灯控制算法的模板

基于sumo实现交通灯控制算法的模板 目录 在windows安装run hello world networkroutesviewsettings & configurationsimulation 交通灯控制系统 介绍文件生成器类&#xff08;FileGenerator&#xff09;道路网络&#xff08;Network&#xff09;辅助函数生成道路网络&am…

SOFA Framework源代码及插件Win11编译开发环境配置

这篇文章主要记录详细的SOFA Framework软件的源代码编译环境配置过程&#xff0c;开发环境基于Win系统&#xff0c;编译完成后&#xff0c;可以在插件或框架的源代码上进行开发集成。本文纯手写输入&#xff0c;言简意赅&#xff0c;以大方向和思路为准&#xff0c;具体需要注意…

什么是Alibaba Cloud Linux?完全兼容CentOS,详细介绍

Alibaba Cloud Linux是基于龙蜥社区OpenAnolis龙蜥操作系统Anolis OS的阿里云发行版&#xff0c;针对阿里云服务器ECS做了大量深度优化&#xff0c;Alibaba Cloud Linux由阿里云官方免费提供长期支持和维护LTS&#xff0c;Alibaba Cloud Linux完全兼容CentOS/RHEL生态和操作方式…

Ubuntu22.04系统安装软件、显卡驱动、cuda、cudnn、pytorch

Ubuntu22.04系统安装软件、显卡驱动、cuda、cudnn、pytorch 安装 Nvidia 显卡驱动安装 CUDA安装 cuDNN安装 VSCode安装 Anaconda 并更换源在虚拟环境中安装 GPU 版本的 PyTorchReference 这篇博文主要介绍的是 Ubuntu22.04 系统中软件、显卡驱动、cuda、cudnn、pytorch 等软件和…

ubuntu 安装 anaconda

ubuntu 安装 anaconda 下载 wget https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh安装 bash Anaconda3-2023.09-0-Linux-x86_64.sh2.1 回车继续 2.2 许可协议 输入 q 退出阅读许可协议 2.3 输入 yes 接受 许可协议 2.4 设置 anaconda 安装位置 如不需…

轻松获取CHATGPT API:免费、无验证、带实例

免费获取和使用ChatGPT API的方法 快速开始&#xff1a;视频教程 章节一&#xff1a;GPT-API-Free开源项目介绍 GPT-API-Free 是一个开源项目&#xff0c;它提供了一个中转API KEY&#xff0c;使用户能够调用多个GPT模型&#xff0c;包括gpt-3.5-turbo、embedding和gpt-4。这…

C#中的值和引用笔记

文章目录 1. 简单介绍2. 如何判断值类型和引用类型3. 语句块4. 变量的生命周期5. 结构体中的值和引用6. 数组中的存储规则7. 结构体继承接口 1. 简单介绍 2. 如何判断值类型和引用类型 在代码中直接转到内部F12 如string类型 值类型int 3. 语句块 4. 变量的生命周期 5. 结构…