完美实现校验:利用Spring Validation实现强大的输入验证

概述

校验例子

大家平时编码中经常涉及参数的校验,对于一个用户注册的方法来说会校验用户名密码信息:

public class UserController {public ResponseEntity<String> registerUser(String username, String password) {if (username == null || username.isEmpty()) {return ResponseEntity.badRequest().body("用户名不能为空");}if (password == null || password.isEmpty()) {return ResponseEntity.badRequest().body("密码不能为空");}if (password.length() < 6) {return ResponseEntity.badRequest().body("密码长度至少为6位");}// 处理用户注册逻辑return ResponseEntity.ok("用户注册成功");}
}

上述例子中需要手动编写参数校验逻辑的过程。虽然对于这个简单的示例而言,手动编写校验逻辑可能是可行的,但是对于复杂的验证规则和多个参数的情况,手动编写校验逻辑会变得冗长、难以维护和复用。

引入现代的校验框架如Spring Validation可以帮助解决这些问题,提供更高效、统一和可维护的参数校验方案。

Bean Validation规范

  • JSR303/JSR-349/JSR-380: JSR303(Bean Validation)是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349(Bean Validation 1.1)是其的升级版本,添加了一些新特性。JSR-380(Bean Validation 2.0,JSR380标准 )对其规范进一步扩展和增强。

  • hibernate validationhibernate validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等

  • Spring validationspring validationhibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中

Bean Validation的主页:http://beanvalidation.org  
Bean Validation的参考实现:https://github.com/hibernate/hibernate-validator

相关版本兼容性

Bean ValidationHibernate ValidationJDKSpring Boot
1.15.4 +6+1.5.x
2.06.0 +8+2.0.x
3.07.0 +9+2.0.x

3.0后Bean Validation改名为Jakarta Bean Validation 3.0了。
如果你的项目版本是jdk1.8的,不要使用hibernate-validator 7.0的版本,它里面的依赖的jakarta.validation-api:3.0是需要jdk1.9的部分支持的。

Spring Validation注解

Spring Validation建立在Java Bean Validation(JSR 380)的基础上,为开发人员提供了一组注解和工具,用于定义和执行数据验证规则。它允许开发人员在应用程序中定义验证规则,并使用这些规则来验证输入数据、请求参数、领域对象等。

@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

常用注解标签如下:

标签说明
@Null限制只能为null
@NotNull限制必须不为null
@AssertFalse限制必须为false
@AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future限制必须是一个将来的日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Past限制必须是一个过去的日期
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在min到max之间
@Past验证注解的元素值(日期类型)比当前时间早
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

hibernate-validator 校验Java Bean

  • pom引入hibernate-validator

<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.2.0.Final</version>
</dependency>
  • 创建一个Java Bean,我们校验一下用户名跟年龄

public class User {@NotBlank(message = "用户名不能为空")private String username;@Min(value = 18, message = "年龄不能小于18岁")private int age;// 构造函数、Getter 和 Setter 方法
}
  • 执行校验

public class ValidatorTest {public static void main(String[] args) {// 创建校验器ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();// 创建用户对象User user = new User();user.setUsername("");user.setAge(16);// 执行校验Set<ConstraintViolation<User>> violations = validator.validate(user);// 处理校验结果if (!violations.isEmpty()) {for (ConstraintViolation<User> violation : violations) {System.out.println(violation.getMessage());}} else {System.out.println("校验通过");}}
}

用Spring Validation提高生产力

Spring Validation引入

添加pom依赖

Spring Validation校验包被独立成了一个starter组件,引入如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

如果是spring-boot-starter-web不用引入了,spring-boot-starter-web 集成了spring-boot-starter-validation,默认可以不加spring-boot-starter-validation,它同时也集成了hibernate-validator

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
hibernate Validator校验器

这里我们定义hibernate校验器用于校验参数

    @Configuration@EnableAutoConfigurationpublic class HibernateValidatorConfiguration {@Beanpublic MethodValidationPostProcessor methodValidationPostProcessor() {MethodValidationPostProcessor processor = new MethodValidationPostProcessor();processor.setValidator(validator());processor.setProxyTargetClass(true);return processor;}@Beanpublic Validator validator() {return Validation.byProvider(HibernateValidator.class).configure().addProperty("hibernate.validator.fail_fast", "true").buildValidatorFactory().getValidator();}}
全局异常处理

每个Controller方法中都如果都写一遍对校验结果信息的处理,使用起来还是很繁琐。可以通过全局异常处理的方式统一处理校验异常。

    @ControllerAdvice@Slf4jpublic class GlobalExceptionHandler {@ExceptionHandler(value = ConstraintViolationException.class)@ResponseBodypublic ApiResult defaultInsuranceExceptionHandler(ConstraintViolationException e) {log.error("校验错误:{}", e.getMessage());return ApiResult.error("error", e.getMessage());}@ExceptionHandler(value = MissingServletRequestParameterException.class)@ResponseBodypublic ApiResult handleMissingServletRequestParameter(MissingServletRequestParameterException ex) {String error = ex.getParameterName() + " 参数为空";return ApiResult.error("error", error);}}

Controller接口Bean校验

首先,假设我们有一个用户注册的请求对象 UserRegistrationRequest,其中包含用户名和密码字段。

@Data
public class UserRegistrationRequest {@NotBlank(message = "用户名不能为空")private String username;@NotBlank(message = "密码不能为空")@Size(min = 6, message = "密码长度至少为6位")private String password;
}

我们使用了两个注解进行参数校验:

  • @NotBlank:该注解用于验证字段不能为空或空格,并可以通过 message 属性指定验证失败时的错误消息。

  • @Size:该注解用于验证字段的长度,我们指定了密码的最小长度为6,并通过 message 属性定义了验证失败时的错误消息。

接下来我们定义一个Controller接口

@RestController
public class UserController {@PostMapping("/register")public ResponseEntity<String> registerUser(@Valid @RequestBody UserRegistrationRequest request) {// 处理用户注册逻辑return ResponseEntity.ok("用户注册成功");}
}

@RequestParam 参数校验

首先需要将MethodValidationPostProcessor设置成cglib代理

processor.setProxyTargetClass(true);

定义一个接口,我们需要对ID进行校验,1<=id<=400

@Validated
public interface ValidClient {@GetMapping("/valid")String queryById(@RequestParam("id") @Min(1) @Max(400) Integer id);}

controller实现

@RestController
public class ValidController implements ValidClient {@Overridepublic String queryById(Integer id) {return "id:" + id;}
}

分组校验

还是看上面那个Controller接口校验的例子,如果我们注册的时候需要校验用户名和密码,重置密码的时候只校验密码该怎么校验呢?这个时候就用到了分组校验了。

  • 首先,我们需要定义一个新的分组,用于更新场景中的验证,例如UpdateGroup

public interface UpdateGroup {
}
  • 接下来,我们需要在UserRegistrationRequest类的username字段上使用@Validated注解,并通过groups属性指定要应用的验证分组。

public class UserRegistrationRequest {@NotBlank(message = "用户名不能为空", groups = {RegistrationGroup.class})private String username;@NotBlank(message = "密码不能为空")@Size(min = 6, message = "密码长度至少为6位")private String password;// 其他字段和方法
}

在上述示例中,我们将@Validated注解应用到username字段,并通过groups属性指定了RegistrationGroup.class,这意味着在注册场景中会进行校验。

  • 我们可以使用@Validated注解来指定不要应用任何验证分组。

@RestController
public class UserController {@PostMapping("/register")public ResponseEntity<String> registerUser(@Validated(RegistrationGroup.class) @RequestBody UserRegistrationRequest request) {// 处理用户注册逻辑return ResponseEntity.ok("用户注册成功");}@PostMapping("/update")public ResponseEntity<String> updateUser(@Validated(UpdateGroup.class) @RequestBody UserRegistrationRequest request) {// 处理用户更新逻辑return ResponseEntity.ok("用户更新成功");}
}

在上述示例中,我们在updateUser方法中使用@Validated(UpdateGroup.class)来指定在更新场景中只执行UpdateGroup分组的验证规则。由于username字段上没有指定groups属性,所以在更新场景中将不会对username字段进行校验。

自定义注解

Spring 的 validation 为我们提供了许多特性,几乎可以满足日常开发中绝大多数参数校验场景了。但是,一个好的框架一定是方便扩展的。有了扩展能力,就能应对更多复杂的业务场景,下面我们自定义一个日期格式校验的注解

定义注解接口

    @Documented@Constraint(validatedBy = DateFormatValidator.class)@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)public @interface DateFormat {//默认错误消息String message() default "时间格式错误";//分组Class<?>[] groups() default {};//默认日期格式String formatter() default "yyyy-MM-dd";//负载Class<? extends Payload>[] payload() default {};@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)@Documented@interface List {DateFormat[] value();}}

定义校验器

public class DateFormatValidator implements ConstraintValidator<DateFormat, String> {protected String dateFormatter;@Overridepublic void initialize(DateFormat constraintAnnotation) {this.dateFormatter = constraintAnnotation.formatter();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {if (StringUtils.isNotBlank(value)) {try {DateUtils.parseDate(value, dateFormatter);} catch (Exception e) {return false;}return true;}return false;}
}

自定义校验注解使用起来和官方注解没有区别,在需要的字段上添加相应注解即可。

public class RequestParam {@DateFormat(message = "日期输入错误")private String beginDate;// 其他字段和方法
}

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

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

相关文章

Python 之 enumerate

enumerate 是一个内置函数&#xff0c;它允许你在遍历一个序列&#xff08;例如一个列表&#xff09;时&#xff0c;同时获取元素的索引和值。 以下是一个使用 enumerate 的例子&#xff1a; fruits [apple, banana, mango] for i, fruit in enumerate(fruits):print(f"E…

UG NX二次开发(C#)-机械管线布置-添加管道路径

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、UG NX中创建线性路径的操作3、采用UG NX二次开发创建线性管线3.1 构建管线上点的数据结构3.2 创建一个链表3.3 创建线性管线的方法3.4 测试结果1、前言 机械管道布局是UG NX的一个应用模…

uniapp、vue、小程序、js图片转base64 示例代码

uniapp是一款跨平台的应用开发框架&#xff0c;基于Vue.js和小程序原生能力进行封装&#xff0c;旨在帮助开发者快速构建跨平台的应用程序。在uniapp中&#xff0c;可以使用Vue.js语法进行页面开发&#xff0c;同时通过使用小程序原生能力&#xff0c;可以实现调用设备的摄像头…

GCC 安装编译linux

1.下载gcc源码 2.配置编译源码 3.安装 下载地址:GCC mirror sites - GNU Project France (no snapshots): ftp.lip6.fr, thanks to ftpmaintlip6.frFrance, Brittany: ftp.irisa.fr, thanks to ftpmaintirisa.frGermany, Berlin: ftp.fu-berlin.de, thanks to ftpfu-berlin…

【数据结构与算法】1.时间复杂度和空间复杂度

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

二进制数据传输中错误的检测与修正

1 文章问题概述 在二进制传输中&#xff0c;由于各种原因&#xff08;如电磁干扰、硬件故障等&#xff09;&#xff0c;数据位可能会发生变化&#xff0c;导致“1”变成“0”&#xff0c;或者“0”变成“1”&#xff0c;这就是所谓的传输错误。 1.1 如何检测错误 一般通过在发…

【论文阅读】GPT4Graph: Can Large Language Models Understand Graph Structured Data?

文章目录 0、基本介绍1、研究动机2、准备2.1、图挖掘任务2.2、图描述语言&#xff08;GDL&#xff09; 3、使用LLM进行图理解流程3.1、手动提示3.2、自提示 4、图理解基准4.1、结构理解任务4.1、语义理解任务 5、数据搜集5.1、结构理解任务5.2、语义理解任务 6、实验6.1、实验设…

史上最全EasyExcel

一、EasyExcel介绍 1、数据导入&#xff1a;减轻录入工作量 2、数据导出&#xff1a;统计信息归档 3、数据传输&#xff1a;异构系统之间数据传输 二、EasyExcel特点 Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内…

以后要做GIS开发的话是学GIS专业还是学计算机专业好一些?

GIS开发其实严格来说分为前后端以及底层开发。不同的方向&#xff0c;代表了不同的开发语言。 所以大家首先要了解自己具体要做的岗位类型是什么&#xff0c;其次才是选择专业侧重点。 但是严格来说&#xff0c;选择某个专业&#xff0c;到就业方向这个过程&#xff0c;并不是…

el-table样式错乱解决方案

bug&#xff1a; 图片的椭圆框住的地方&#xff0c;在页面放大缩小之后就对不齐了。 原因&#xff1a; 主要原因是当你对页面放大缩小的时候&#xff0c;页面进行了重构&#xff0c;页面的宽高及样式进行了变化&#xff0c;但是在这个更新的过程中&#xff0c;table的反应并没…

Redis: Redis介绍

文章目录 一、redis介绍二、通用的命令三、数据结构1、字符串类型&#xff08;String&#xff09;&#xff08;1&#xff09;介绍&#xff08;2&#xff09;常用命令&#xff08;3&#xff09;数据结构 2、列表&#xff08;List&#xff09;&#xff08;1&#xff09;介绍&…

CompletableFuture**应用源码分析(三)

2.3 CompletableFuture源码分析 CompletableFuture的源码内容特别多。不需要把所有源码都看了&#xff0c;更多的是要掌握整个CompletableFuture的源码执行流程&#xff0c;以及任务的执行时机。 从CompletableFuture中比较简单的方法作为分析的入口&#xff0c;从而掌握整体执…

python实操之网络爬虫介绍

一、什么是网络爬虫 网络爬虫&#xff0c;也可以叫做网络数据采集更容易理解。它是指通过编程向网络服务器&#xff08;web&#xff09;请求数据&#xff08;HTML表单&#xff09;&#xff0c;然后解析HTML&#xff0c;提取出自己想要的数据。 它包括了根据url获取HTML数据、解…

R.swift SwiftGen 资源使用指南

R.swift 和 SwiftGen 资源转换使用指南 R.swift &#xff08;原始代码会打包到项目&#xff1f;&#xff09; Pod platform :ios, 12.0 target LBtest do# Comment the next line if you dont want to use dynamic frameworksuse_frameworks!pod R.swift # pod SwiftGen, ~&g…

ConcurrentHashMap和HashMap的区别

什么是HashMap &#xff08;1&#xff09;HashMap 是基于 Map 接口的非同步实现&#xff0c;线程不安全&#xff0c;是为了快速存取而设计的&#xff1b;它采用 key-value 键值对的形式存放元素&#xff08;并封装成 Node 对象&#xff09;&#xff0c;允许使用 null 键和 nul…

笨蛋学设计模式行为型模式-观察者模式【14】

行为型模式-观察者模式 8.1观察者模式:arrow_up::arrow_up::arrow_up:8.1.1概念8.1.2场景8.1.3优势 / 劣势8.1.4观察者模式可分为观察者的基本结构&#xff1a; 8.1.5观察者模式8.1.6实战8.1.6.1题目描述8.1.6.2输入描述8.1.6.3输出描述8.1.6.4代码 8.1.7总结 8.1观察者模式⬆️…

(二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真

一、创建工作空间 mkdir catkin_ws cd catkin_ws mkdir src cd src 二、下载wpr_simulation源码 git clone https://github.com/6-robot/wpr_simulation.git 三、编译 ~/catkin_make 目录下catkin_makesource devel/setup.bash 四、运行 roslaunch wpr_simulation wpb_s…

【Backbone】Vim(Vision Mamba)架构学习笔记

1 学习资料 论文&#xff1a;《Vision Mamba: Efficient Visual Representation Learning with Bidirectional State Space Model》 阅读资料 【博文】《入局CV&#xff0c;Mamba再显神威&#xff01;华科王兴刚团队首次将Mamba引入ViT&#xff0c;更高精度、更快速度、更低…

java小项目:简单的收入明细记事本,超级简单(不涉及数据库,通过字符串来记录)

一、效果 二、代码 2.1 Acount类 package com.demo1;public class Acount {public static void main(String[] args) {String details "收支\t账户金额\t收支金额\t说 明\n"; //通过字符串来记录收入明细int balance 10000;boolean loopFlag true;//控制循…

2023.1.19 关于 Redis 事务详解

目录 Redis 事务对比 MySQL 事务 MySQL 事务 Redis 事务 Redis 事务原子性解释 Redis 事务详解 执行流程 典型使用场景 Redis 事务命令 WATCH 的使用 WATCH 实现原理 总结 阅读下文之前建议点击下方链接了解 MySQL 事务详解 MySQL 事务详解 Redis 事务对比 MySQL 事…