Spring Validation实践及其实现原理

Bean Validation 2.0 注解

校验空值
@Null:验证对象是否为 null
@NotNull:验证对象是否不为 null
@NotEmpty:验证对象不为 null,且长度(数组、集合、字符串等)大于 0
@NotBlank:验证字符串不为 null,且去除两端空白字符后长度大于 0
校验大小
@Size(min=, max=):验证对象(数组、集合、字符串等)长度是否在给定的范围之内
@Min(value):验证数值(整数或浮点数)是否大于等于指定的最小值
@Max(value):验证数值是否小于等于指定的最大值
校验布尔值
@AssertTrue:验证 Boolean 对象是否为 true
@AssertFalse:验证 Boolean 对象是否为 false
校验日期和时间
@Past:验证 DateCalendar 对象是否在当前时间之前
@Future:验证 DateCalendar 对象是否在当前时间之后
@PastOrPresent:验证日期是否是过去或现在的时间
@FutureOrPresent:验证日期是否是现在或将来的时间
正则表达式
@Pattern(regexp=, flags=):验证 String 对象是否符合正则表达式的规则
Hibernate Validation 拓展
@Length(min=, max=):验证字符串的大小是否在指定的范围内
@Range(min=, max=):验证数值是否在合适的范围内
@UniqueElements:校验集合中的值是否唯一,依赖于 equals 方法
@ScriptAssert:利用脚本进行校验

@Valid 和 @Validated
这两个注解是校验的入口,作用相似但用法上存在差异。

@Validated

// 用于类/接口/枚举,方法以及参数
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)
@Documented  
public @interface Validated {  // 校验时启动的分组  Class<?>[] value() default {};  
}

@Valid

// 用于方法,字段,构造函数,参数,以及泛型类型  
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })  
@Retention(RUNTIME)  
@Documented
public @interface Valid {  // 未提供其他属性  
}

作用范围不同:@Validated 无法作用在于字段, @Valid 无法作用于类;
注解中的属性不同:@Validated 中提供了指定校验分组的属性,而 @Valid 没有这个功能,因为 @Valid 不能进行分组校验。
字段校验场景及使用示例
常见的校验场景有三种: Controller 层的校验、编程式校验、 Dubbo 接口校验。

Controller层 的校验
使用方式
当方法入参为 @RequestBody 注解的 JavaBean,可在入参前使用 @Validated 或 @Valid 注解开启校验。

@PostMapping("/save")
public Response<Boolean> saveNotice(@Validated @RequestBody NoticeDTO noticeDTO) {// noticeDTO中各字段校验通过,才会执行后续业务逻辑return Response.ok(true);
}

当方法入参为 @PathVariable、 @RequestParam 注解的简单参数时,需要在 Controller 加上 @Validated 注解开启校验。

@RequestMapping("/notice")
@RestController
// 必须加上该注解
@Validated
public class UserController {// 路径变量@GetMapping("{id}")public Reponse<NoticeDTO> detail(@PathVariable("id") @Min(1L) Long noticeId) {// 参数noticeId校验通过,执行后续业务逻辑return Reponse.ok();}// 请求参数@GetMapping("getByTitle")public Result getByTitle(@RequestParam("title") @Length(min = 1, max = 20) String  title) {// 参数title校验通过,执行后续业务逻辑return Result.ok();}
}

原理
Spring 框架中的 HandlerMethodArgumentResolver 策略接口,负责将方法参数解析为特定请求中的参数值。

public interface HandlerMethodArgumentResolver {  // 判断当前解析器是否支持给定的方法参数boolean supportsParameter(MethodParameter var1);  @Nullable  // 实际解析参数的方法Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
上述接口针对 @RequestBody 的实现类 RequestResponseBodyMethodProcessor 中,存在字段校验逻辑,调用 validateIfApplicable 方法校验参数。
// RequestResponseBodyMethodProcessor
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  // 前置处理// 校验逻辑if (binderFactory != null) {  WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);  if (arg != null) {//调用校验函数this.validateIfApplicable(binder, parameter);  if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {  throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());  }  }// 数据绑定逻辑}// 返回处理结果return this.adaptArgumentIfNecessary(arg, parameter);  
}

validateIfApplicable 方法中,根据方法参数上的注解,决定是否进行字段校验:当存在 @Validated 或以 Valid 开头的注解时,进行校验。

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {// 获取参数上的注解Annotation[] annotations = parameter.getParameterAnnotations();  Annotation[] var4 = annotations;  int var5 = annotations.length;  // 遍历注解for(int var6 = 0; var6 < var5; ++var6) {  Annotation ann = var4[var6];  // 获取 @Validated 注解Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(ann, Validated.class); // 或者注解以 Valid 开头 if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {// 开启校验Object hints = validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann);  Object[] validationHints = hints instanceof Object[] ? (Object[])((Object[])hints) : new Object[]{hints};  binder.validate(validationHints);  break;  }  }  
}

@PathVariable 和 @RequestParam 对应的实现类中,则没有相应字段校验逻辑,因此需要在 Controller 上使用 @Validated,开启字段校验。

编程式校验
配置 Validator

@Configuration  
public class ValidatorConfiguration {  @Bean  public Validator validator() {  ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)  .configure()  // 设置是否开启快速失败模式  //.failFast(true)  .buildValidatorFactory();  return validatorFactory.getValidator();  }  
}
获取 validator 并校验
public class TestValidator {// 注入验证器@Resourceprivate javax.validation.Validator validator;public String testMethod(TestRequest request) {// 进行校验,获取校验结果Set<ConstraintViolation<TestRequest>> constraintViolations = validator.validate(request);// 组装校验信息并返回return res;}
}
Dubbo 接口校验
可在 @DubboService 注解中,设置 validation 参数为 true,开启生产者的字段验证。
@DubboService(version = "1.0.0", validation="true")
public class DubboApiImpl implements DubboApi {....
}

该方式返回的信息对使用者不友好,可通过 Dubbo 的 filter 自定义校验逻辑和返回信息。需要注意的是,在 Dubbo 中有自己的 IOC 实现来控制容器,因此需提供 setter 方法,供 Dubbo 调用。

@Activate(  group = {"provider"},  value = {"customValidationFilter"},  order = 10000  
)  
@Slf4j  
public class CustomValidationFilter implements Filter {  private javax.validation.Validator validator;  // duubo会调用setter获取beanpublic void setValidator(javax.validation.Validator validator) {  this.validator = validator;  } public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {  if (this.validator != null && !invocation.getMethodName().startsWith("$")) {  // 补充字段校验,返回信息的组装以及异常处理}  return invoker.invoke(invocation);  }  
}

进阶使用
分组校验
对于同一个 DTO, 不同场景下对其校验规则可能不同, @Validted 支持按照分组分别验证,示例代码如下:

校验注解的 groups 属性中添加分组

@Data
public class NoticeDTO {@Min(value = 0L, groups = Update.class)private Long id;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String title;// 保存的时候校验分组public interface Save {}// 更新的时候校验分组public interface Update {}
}
@Validted 上指定分组
@PostMapping("/save")
public Response<Boolean> saveNotice(@RequestBody @Validated(NoticeDTO.Save.class) NoticeDTO noticeDTO) {// 分组为Save.class的校验通过,执行后续逻辑return Response.ok();
}@PostMapping("/update")
public Response<Boolean> updateNotice(@RequestBody @Validated(NoticeDTO.Update.class) NoticeDTO noticeDTO) {// 分组为Update.class的校验通过,执行后续逻辑return Response.ok();
}

自定义校验注解
如果我们想自定义实现一些验证逻辑,可以使用自定义注解,主要包括两部分:实现自定义注解,实现对应的校验器 validator。下面尝试实现一个注解,用于校验集合中的指定属性是否存在重复,代码如下:

实现校验注解,主要需要包含 message()、 filed()、 groups() 三个方法,功能如注释所示。

@Target({ElementType.FIELD, ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
// 指定校验器
@Constraint(validatedBy = UniqueValidator.class)  
public @interface Unique {  // 用于自定义验证信息String message() default "字段存在重复";  // 指定集合中的待校验字段String[] field();  // 指定分组Class<?>[] groups() default {};  
}

实现对应的校验器,主要校验逻辑在 isValid 方法:获取集合中指定字段,并组装为 set,比较 set 和集合的长度,以判断集合中指定字段是否存在重复。

// 实现ConstraintValidator<T, R>接口,T为注解的类型,R为注解的字段类型
public class UniqueValidator implements ConstraintValidator<Unique, Collection<?>> {  private Unique unique;  @Override  public void initialize(Unique constraintAnnotation) {  this.unique = constraintAnnotation;  }  @Override  public boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {// 集合为空直接校验通过if (collection == null || collection.size() == 0) {  return Boolean.TRUE;  }  // 从集合中获取filed中指定的待校验字段,看是否存在重复return Arrays.stream(unique.field())  .filter(fieldName -> fieldName != null && !"".equals(fieldName.trim()))  .allMatch(fieldName -> {// 收集集合collection中字段为fieldName的值,存入set并计算set的元素个数countint count = (int) collection.stream()  .filter(Objects::nonNull)  .map(item -> {  Class<?> clazz = item.getClass();  Field field;  try {  field = clazz.getField(fieldName);  field.setAccessible(true);  return field.get(item);  } catch (Exception e) {  return null;  }  })  .collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size)); // set中元素个数count与集合长度比较,若不相等则说明collection中字段存在重复,校验不通过if (count != collection.size()) {  return false;  }  return true;  });  }  
}

总结
通过本文我们得以了解 Spring Validation 的机理及其在实际项目中的应用。无论是标准的校验注解,还是自定义的校验逻辑, Spring Validation 都为开发者提供了高效且强大的校验工具。总的来说, Spring Validation 是任何 Spring 应用不可或缺的一部分,对于追求高质量代码的 JAVA 开发者而言,掌握其用法和最佳实践至关重要。

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

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

相关文章

2023软件应用类下载系统平台源码/手机软件应用、新闻资讯下载站/软件库网站源码

源码简介&#xff1a; 这个是最新软件应用类平台源码、手机应用下载系统源码、软件应用市场下载站源码、新闻资讯软件下载。2023软件应用类平台源码/手机软件应用、新闻资讯下载站&#xff0c;它是软件库网站源码。 最新软件应用类平台源码 手机应用下载系统源码 软件应用市场…

NX二次开发UF_CURVE_add_faces_ocf_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_add_faces_ocf_data Defined in: uf_curve.h int UF_CURVE_add_faces_ocf_data(tag_t face_tag, UF_CURVE_ocf_data_p_t uf_offset_data ) overview 概述 Add a face col…

JVMj之console Java监视与管理控制台

jconsole Java监视与管理控制台 1、jconsole介绍 jconsole (java monitoring and management console)是一款基于JMX (Java Management Extensions) 的可视化监视和管理工具。 2、启动jconsole 1、在linux和windwos下通过jconsole启动即可。 2、然后会自动搜索本机运行的…

【开源】基于Vue和SpringBoot的独居老人物资配送系统

项目编号&#xff1a; S 045 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S045&#xff0c;文末获取源码。} 项目编号&#xff1a;S045&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询社区4…

Linux(6):文件与文件系统的压缩,打包与备份

压缩文件的用途与技术 由于 1 byte 8 bits &#xff0c;所以每个byte当中会有8个空格&#xff0c;而每个空格可以是0,1。 其实文件里面有相当多的『空间』存在&#xff0c;并不是完全填满的&#xff0c;而『压缩』的技术就是将这些『空间』填满&#xff0c;以让整个文件占用…

微信小程序商城实例mpvue-xbyjShop-master(附精选源码32套,涵盖商城团购等)

mpvue-xbyjShop 基于mpvue的微信小程序商城&#xff08;小程序端&#xff0c;服务端&#xff09; 小程序端 技术栈 mpvue mpvue-router-patch mpvue-entry vuex webpack ES6/7 flyio mpvue-wxparse 项目运行 微信开发中工具选中mpvue-xbyjShop/buyer作为项目目录即可功…

JavaScript的学习

HTML的学习-CSDN博客 从html的学习中 其实我已经用到了 JavaScript的脚本 &#xff08;GPT&#xff09; 例如 echo <script>alert("账号密码错误"); window.location"index.html";</script>; 弹窗 然后定位到 index.html 这里能够让我们更…

品牌线上窜货查的出来吗

如果窜货不治理会出现什么局面&#xff0c;显见的当然是渠道混乱&#xff0c;低价丛生&#xff0c;严重的还会导致真假混卖&#xff0c;最后所有的后果都会由品牌承担&#xff0c;口碑的影响是必然的&#xff0c;那品牌的衰败也会是一种趋势&#xff0c;所以治理窜货是品牌发展…

C现代方法(第22章)笔记——输入/输出

文章目录 第22章 输入/输出22.1 流22.1.1 文件指针22.1.2 标准流和重定向22.1.3 文本文件与二进制文件 22.2 文件操作22.2.1 打开文件22.2.2 模式22.2.3 关闭文件22.2.4 为打开的流附加文件22.2.5 从命令行获取文件名22.2.6 临时文件22.2.7 文件缓冲22.2.8 其他文件操作 22.3 格…

Windows | 模仿网易云任务栏实现自定义按钮及缩略图

前言 最近更新网易云发现任务栏按钮中除了播放相关的按钮&#xff0c;多了一个喜欢的按钮&#xff1a; 之前我一直以为网易云任务栏的按钮只是 Windows 为音乐软件专门提供的&#xff0c;于是我又看了一眼系统自带的播放器&#xff0c;发现并没有爱心按钮&#xff1a; 这时我就…

计算给定字符串中各个数字的和的平均值…… ← Python 列表

【题目描述】 给定字符串 s"9876543210"。 请编程计算给定字符串中各个数字的和的平均值&#xff0c;并统计大于平均值的数字个数。【算法分析】 ◆ alist("abcd") # Create a list with characters a, b, c, d◆ eval(a[i]) # Converts characters to i…

C在国内就业已经拉胯,ChatGPT告诉我的

文章目录 一、前言二、ChatGPT查到的数据三、数据亮点 1.C语言近3年数据大跌2.招聘数量每年都在剧增的是全栈工程师3.薪资涨幅最高的是全栈和网安 四、结语 一、前言 不仅前在微信群里搭建了一个ChatGPT 5.0做智能助手&#xff0c;让他来帮我回答群问题&#xff0c; 搭建好…

数十亿美元商机!英国数字基础设施公司Equinix与法国量子计算公司Alice Bob 合作

​&#xff08;图片来源&#xff1a;网络&#xff09; 近日&#xff0c;全球数字基础设施公司Equinix宣布与全球领先的法国量子计算公司Alice & Bob合作&#xff0c;旨在共同开发市场上最为可靠的量子处理器之一。此次合作将使Equinix公司的客户通过使用Equinix Metal和Eq…

好的程序员有什么特质呢?

程序员想要提升自己&#xff0c;一定要关注到工作中的方方面面。而一个好的程序员&#xff0c;一般都有这些特质&#xff1a; 弱者抱怨环境&#xff0c;强者改变环境 不要试图通过抱怨环境来获得工作环境上的改变&#xff0c;这不仅不会给你带来任何实质性的改变&#xff0c;…

自定义字符-摄氏度汉字一

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

springboot+vue项目如何集成onlyoffice开源文档组件

一、onlyoffice是什么 ONLYOFFICE 是一个开源的办公套件&#xff0c;适合多人在线协作。由总部位于总部在拉脱维亚的 IT 公司Acensio System SIA 开发。它提供在线协作文档编辑器&#xff08;包括文档、电子表格、演示文稿和表单&#xff09;&#xff0c;适用于 Windows、Linu…

python tkinter使用(五)

python tkinter使用(五) 本篇文章讲述tkinter 中treeview的使用 Treeview是一个多列列表框&#xff0c;可以显示层次数据。 #!/usr/bin/python3 # -*- coding: UTF-8 -*- """Author: zhTime 2023/11/23 下午8:28 .Email:Describe: treeview 使用 "&quo…

项目经理面试题持续更新

1.项目中常用的文档有哪些&#xff1f; 1、可行性报告 可行性报告的目的是调查和展示任务要求&#xff0c;并确定项目是否值得和可行。可行性由五个主要因素验证——技术和系统、成本、法律、运营和进度。次要可行性因素包括市场、资源和文化因素。 2、项目章程 项目章程是证明…

Linux上自动挂载windows下的网络共享文件夹

比如我们想在ubuntu上挂载一个windows的共享文件夹&#xff0c;我们可以用如下方式实现。 首先我们将windows下的文件夹右键选择【属性】&#xff0c;然后选择【共享】。 选择需要共享的用户&#xff0c;然后设置权限级别。 点击共享&#xff0c;然后点击完成。 这样我们在wi…

Go语言网络爬虫工程经验分享:pholcus库演示抓取头条新闻的实例

网络爬虫是一种自动从互联网上获取数据的程序&#xff0c;它可以用于各种目的&#xff0c;如数据分析、信息检索、竞争情报等。网络爬虫的实现方式有很多&#xff0c;不同的编程语言和框架都有各自的优势和特点。在本文中&#xff0c;我将介绍一种使用Go语言和pholcus库的网络爬…