「SpEL Validator」使用指南(一套无敌的参数校验组件)

前言

这是一套全新的参数校验组件,并非造轮子。

看完本文你可能会觉得用不上或不屑于使用,但这玩意确实有应用场景,你不妨稍微留意一下,日后你总会发现有用得上的时候。

此乃系列文章,当前为第②篇,其他文章地址:

①我写了一套无敌的参数校验组件:https://blog.csdn.net/little_stick_i/article/details/138540678

简介

SpEL Validator 是一个强大的 Java 参数校验包,基于 SpEL 实现,扩展自 javax.validation 包,用于简化参数校验,几乎支持所有场景下的参数校验。

GitHub地址:https://github.com/stick-i/spel-validator

本组件的目的不是代替 javax.validation 的校验注解,而是作为一个扩展,方便某些场景下的参数校验。

添加依赖

当前版本:0.2.0-beta。最新版本建议到GitHub查看。

大多数情况下,你只需要添加以下两个依赖:

<dependencys><dependency><groupId>cn.sticki</groupId><artifactId>spel-validator</artifactId><version>Latest Version</version></dependency><!-- 基于 javax.validation 标准的校验器实现包 --><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.2.5.Final</version></dependency>
</dependencys>

如果你的项目中没有任何对于Spring的依赖,那么你需要额外添加一个对SpEL的依赖:

<dependencys><!-- SpEL 依赖项 --><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>${spring.version}</version></dependency>
</dependencys>

开启约束校验

需要满足以下两个条件,才会对带注解的元素进行校验:

  1. 在接口参数上使用 @Valid@Validated 注解
  2. 在实体类上使用 @SpelValid 注解
@RestController
@RequestMapping("/example")
public class ExampleController {@PostMapping("/simple")public Resp<Void> simple(@RequestBody @Valid /*添加启动注解*/ SimpleExampleParamVo simpleExampleParamVo) {return Resp.ok(null);}}@Data
@SpelValid /*添加启动注解*/
public class SimpleExampleParamVo {// ...
}

如果只满足第一个条件,那么只会对带 @NotNull@NotEmpty@NotBlank 等注解的元素进行校验。

如果只满足第二个条件,那么不会对任何元素进行校验。

这是因为 @SpelValid 注解是基于 javax.validation.Constraint 实现的。
这就意味着,@SpelValid@NotNull@NotEmpty@NotBlank 等注解一样,
需要在 @Valid@Validated 注解的支持下才会生效。

SpEL Validator 提供的约束注解又是在 @SpelValid 的内部进行校验的,只有在 @SpelValid 注解生效的情况下才会执行约束校验。

所以,如果需要使用 SpEL Validator 进行校验,需要同时满足上述两个条件。

设置开启条件

@SpelValid 注解包含一个属性 condition,支持 SpEL 表达式,计算结果必须为 boolean 类型。

表达式的算结果为true 时,表示开启校验,默认情况下是开启的。

@Data
@SpelValid(condition = "1 > 0") /*设置开启校验的条件*/
public class SimpleExampleParamVo {// ...
}

这里的 condition 字段同样支持上下文引用,具体使用方式参考 引用上下文字段。

使用约束注解

目前支持的约束注解有:

注解说明对标 javax.validation
@SpelAssert逻辑断言校验@AssertTrue
@SpelNotNull非 null 校验@NotNull
@SpelNotEmpty集合、字符串、数组大小非空校验@NotEmpty
@SpelNotBlank字符串非空串校验@NotBlank
@SpelNull必须为 null 校验@Null
@SpelSize集合、字符串、数组长度校验@Size
@SpelMin即将支持@Min
@SpelMax即将支持@Max

所有约束注解都包含三个默认的属性:

  • condition:约束开启条件,支持 SpEL 表达式,表达式的计算结果必须为 boolean 类型,当 计算结果为true 时,才会对带注解的元素进行校验,默认情况下开启。
  • message:校验失败时的提示信息。
  • group:分组条件,支持 SpEL 表达式,当分组条件满足时,才会对带注解的元素进行校验。具体使用方式参考 分组校验。

在需要校验的字段上使用 @SpelNotNull 等约束注解。

@Data
@SpelValid
public class SimpleExampleParamVo {/*** 此处使用了 @SpelNotNull 注解* 当参数 condition 的计算结果 true 时,会启用对当前字段的约束,要求为当前字段不能为null* 约束校验失败时,提示信息为:语音内容不能为空*/@SpelNotNull(condition = "true", message = "语音内容不能为空")private Object audioContent;}

引用上下文字段

设计这套组件的初衷,就是为了满足一些需要判断另一个字段的值来决定当前字段是否校验的场景。

在组件内部,将当前校验的整个类对象作为了 SpEL 表达式解析过程中的根对象,所以在表达式中可以直接引用类中的任意字段。

通过 #this.fieldName 的方式来引用当前类对象的字段。

@Data
@SpelValid
public class SimpleExampleParamVo {private boolean switchAudio;/*** 此处引用了上面的 switchAudio 字段* 当 switchAudio 字段的值为 true 时,才会校验 audioContent 是否为null*/@SpelNotNull(condition = "#this.switchAudio == true")private Object audioContent;}

分组校验

启动注解 @SpelValid 上包含一个属性 spelGroups,类型为字符串数组,支持 SpEL 表达式。

每一个约束注解上也都包含一个属性 group,类型为字符串数组,支持 SpEL 表达式。

为什么 @SpelValid 注解上的 spelGroups 属性不叫 groups?

因为 @SpelValid 注解是基于 javax.validation.Constraint 实现的,而 Constraint 中已经有一个 groups 属性了,故命名为 spelGroups

在使用 @SpelValid 的时候,你可以同时使用 groupsspelGroups 属性,但是 groups 属性只能用于 javax.validation 的分组校验。
@SpelValid@NotNull@NotEmpty 等注解是兄弟关系,它的 groups 属性同样受上层 @Valid@Validated 注解的影响。

默认情况下,@SpelValid.spelGroups 为空,表示不进行分组校验,此时所有的约束注解都会生效。

@SpelValid.spelGroups 不为空时,表示开启分组校验,此时:

  • 约束注解中的 group 属性为空时,该约束注解生效。
  • 约束注解中的 group 属性不为空时,只有当 @SpelValid.spelGroups 中的分组信息与此处的分组信息有交集时,才会对带注解的元素进行校验。

这里表达式的计算结果可以是任何类型,但只有两个计算结果满足 o.equals(e) 时,才被认为是相等的。

使用示例:

@Data
@SpelValid(spelGroups = "#this.type")
public class GroupExampleParamVo {@NotNull@Pattern(regexp = "^text|audio$")private String type;/*** 当 type 字段的值为 text 时,才会对此字段进行校验*/@SpelNotNull(group = "'text'")private Object textContent;/*** 当 type 字段的值为 audio 时,才会对此字段进行校验*/@SpelNotNull(group = "'audio'")private Object audioContent;/*** 未指定分组,默认被校验*/@SpelNotNullprivate Integer other;}

嵌套校验

本组件支持嵌套校验,在需要校验的字段上添加 @Valid,以及在另一个类上添加 @SpelValid 注解。

@Data
@SpelValid
public class TestParamVo {private Boolean switchVoice;@SpelNotNull(condition = "#this.switchVoice == true")private Object voiceContent;@Validprivate TestParamVo2 testParamVo2;}@Data
@SpelValid /*在此处添加注解*/
public class TestParamVo2 {@SpelNotNullprivate Object object;}

或者将 @SpelValid 注解转移到对应的字段上。

@Data
@SpelValid
public class TestParamVo {private Boolean switchVoice;@SpelNotNull(condition = "#this.switchVoice == true")private Object voiceContent;@Valid@SpelValid /*在此处添加注解*/private TestParamVo2 testParamVo2;}@Data
public class TestParamVo2 {@SpelNotNullprivate Object object;}

处理约束异常

当校验失败时,本组件会将异常信息上报到 javax.validation 的异常体系中。

正常情况下,你只需要处理 org.springframework.web.bind.MethodArgumentNotValidException
org.springframework.validation.BindException 这两个校验异常类就好了 ,而无需额外处理本组件的异常信息。

事实上,MethodArgumentNotValidException 继承自 BindException,只需要处理 BindException 就可以了。

@RestControllerAdvice
public class ControllerExceptionAdvice {@ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})public Resp<Void> handleBindException(BindException ex) {String msg = ex.getFieldErrors().stream().map(error -> error.getField() + " " + error.getDefaultMessage()).reduce((s1, s2) -> s1 + "," + s2).orElse("");return new Resp<>(400, msg);}}

处理业务异常

由于本组件支持 调用静态方法调用Spring Bean方法,故在校验过程中可能会抛出除约束异常以外的其他业务异常。

举一个不太恰当的例子

以下是一个枚举类,它包含了一个静态方法,用于根据code获取枚举值,如果获取不到则抛出业务异常:

@Getter
public enum ExampleEnum {XXX(1);private final Integer code;ExampleEnum(Integer code) {this.code = code;}/*** 通过code获取枚举值,如果code不存在则抛出业务异常*/public static ExampleEnum getByCode(Integer code) {for (ExampleEnum value : values()) {if (value.code.equals(code)) {return value;}}throw new BusinessException(400, "枚举值不合法");}}

以下是一个参数类,它包含了一个枚举字段校验,在表达式中引用了上面的枚举类:

@Data
@SpelValid
public class ParamTestBean {/*** 枚举值校验* <p>* 通过静态方法调用,校验枚举值是否存在*/@SpelAssert(assertTrue = "T(cn.sticki.validator.spel.enums.ExampleEnum).getByCode(#this.testEnum)")private Integer testEnum;}

ParamTestBean 校验失败时,我们希望它抛出一个业务异常 BusinessException,但实际上会得到一个 ValidationException

在这里插入图片描述

由于本组件的特殊性,所有抛出的异常信息最终都会被我们下层的校验器捕获,然后包一层 javax.validation.ValidationException 再抛出。

要从框架层面去解决这个问题,只能够脱离 javax.validation 的规范和 hibernate 的执行器来进行校验,
目前看来这样做的成本比较大,且会带来一些其他的影响,故暂时不考虑这样做。

解决方案

当捕获到 ValidationException 时,首先判断下 e.getCause() 的类型是不是自己项目中的业务异常基类,如果是业务异常的类型,就丢给对应的方法去处理,像这样:

@RestControllerAdvice
public class ControllerExceptionAdvice {@ExceptionHandler({BusinessException.class})public Resp<Void> handleBusinessException(BusinessException ex) {return new Resp<>(ex.getCode(), ex.getMessage());}@ExceptionHandler({ValidationException.class})public Resp<Void> handleValidationException(ValidationException ex) {if (ex.getCause() instanceof BusinessException) {return handleBusinessException((BusinessException) ex.getCause());}return new Resp<>(500, "system error");}}

当然这种方案也有缺点,需要将多种不同的异常类型都进行特殊处理,比较麻烦。

本人不才,目前只能想到这种方案,如果你有更好的解决方案,欢迎到 GitHub 提 issue。

开启对 Spring Bean 的支持

默认情况下,解析器无法识别 SpEL 表达式中的 Spring Bean。

如果需要在 SpEL 表达式中调用 Spring Bean,需要在任意一个被 Spring 托管的类上添加 @EnableSpelValidatorBeanRegistrar 注解,
开启 Spring Bean 支持。

@EnableSpelValidatorBeanRegistrar /*添加注解*/
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

开启 Spring Bean 支持后,即可在 SpEL 表达式中调用 Spring Bean。

调用 Bean 的语法为:@beanName.methodName(参数)

@Data
@SpelValid
public class SimpleExampleParamVo {/*** 调用 userService 的 getById 方法,判断用户是否存在* 校验失败时,提示信息为:用户不存在* 这里只是简单举例,实际开发中不建议这样判断用户是否存在*/@SpelAssert(assertTrue = "@userService.getById(#this.userId) != null", message = "用户不存在")private Long userId;}

后记

OK兄弟们,如果你能看到这里,说明你对这个组件还算有点兴趣,不妨到GitHub给它点个star,或者给我点个关注,以便于持续关注项目更新动向,谢谢你的支持~~

GitHub地址:https://github.com/stick-i/spel-validator

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

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

相关文章

用Python实现时间序列模型实战——Day 8: 季节性ARIMA模型 (SARIMA)

一、学习内容 1. SARIMA 模型的定义与公式推导 SARIMA 模型&#xff1a; SARIMA 模型是扩展了 ARIMA 模型的一种方法&#xff0c;全称为季节性自回归积分滑动平均模型&#xff08;Seasonal AutoRegressive Integrated Moving Average&#xff09;。它结合了 ARIMA 模型的非季…

sed awk 第二版学习(二)—— 正则表达式语法

目录 一、表达式 二、成行的字符 1. 反斜杠 2. 通配符 3. 编写正则表达式 4. 字符类 &#xff08;1&#xff09;字符的范围 &#xff08;2&#xff09;排除字符类 &#xff08;3&#xff09;POSIX 字符类补充 5. 重复出现的字符 6. 匹配单词 7. gres 替换脚本 8. …

超越卷积滤波器,HyCoT利用Transformer捕捉高光谱图像的全局依赖性 !

近年来&#xff0c;基于学习的高光谱图像&#xff08;HSI&#xff09;压缩模型的开发引起了大量关注。现有的模型主要使用卷积滤波器&#xff0c;仅捕捉局部依赖性。 此外&#xff0c;它们通常会带来高昂的训练成本&#xff0c;并具有较大的计算复杂性。 为了解决这些问题&…

upload-labs通关攻略

Pass-1 这里上传php文件说不允许上传 然后咱们开启抓包将png文件改为php文件 放包回去成功上传 Pass-2 进来查看提示说对mime进行检查 抓包把这里改为image/jpg; 放包回去就上传成功了 Pass-3 这里上传php文件它说不允许上传这些后缀的文件 那咱们就可以改它的后缀名来绕过…

KinectFusion

1.KinectFusion 笔记来源&#xff1a; 论文地址&#xff1a;KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera* 项目地址&#xff1a;github/KinectFusion [1] 截断符号距离 | TSDF, Truncated Signed Distance Function 本篇对Kinec…

工业储能柜内部运行状态监测装置

工商业储能是用户侧储能的主要应用之一&#xff0c;其核心场景包括峰谷套利、需&#xff08;容&#xff09;量管理、应急备电、动态增容和需求侧响应。为了实现这些功能并确保储能系统的安全、可靠与经济运行&#xff0c;储能集成厂家必须关注多个方面&#xff0c;其中储能设备…

WPF ToolkitMVVM IOC IServiceConllection

用微软自带的 IOC 需要安装 using Microsoft.Extensions.DependencyInjection; using System.Configuration; using System.Data; using System.Windows;namespace WpfApp3 {/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class…

BITCN合集(BITCN 、BITCN-GRU、BITCN-BIGRU、BITCN-LSTM、BITCN-BILSTM、BITCN-SVM)

BITCN合集&#xff08;BITCN 、BITCN-GRU、BITCN-BIGRU、BITCN-LSTM、BITCN-BILSTM、BITCN-SVM&#xff09; BITCN合集&#xff08;BITCN 、BITCN-GRU、BITCN-BIGRU、BITCN-LSTM等&#xff09;代码获取戳此处代码获取戳此处代码获取戳此处 BITCN&#xff08;双向时间卷积神经网…

uniapp引入最新版Animate.css及使用示例

亲测可用,不好用请移至评论区揍我 动画库官网:https://animate.style/ cdn地址:https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css(截至目前最新版为:v4.1.1) 1. 将css下载后导入项目static目录中 2. 重要!修改下载的css文件内容 文件内容如…

vue脚手架路由快速入门

这里写目录标题 路由 router路由插件的引用离线在线CDN 单独路由使用案例项目中如何定义路由1\安装路由2\创建路由文件router.js以及创建相关文件3\应用插件main.js4\实现切换<router-link></router-link>5\展示位置 <router-view></router-view> 嵌套…

在线绘制哑铃图(dumbbell chart)展示基因拷贝数变异(CNV)

导读&#xff1a; 哑铃图的名称来源于其形状&#xff0c;它看起来像一个哑铃&#xff0c;有两个圆形的“重量”在两端&#xff0c;通过一根“杆”连接。常用于展示两个或多个数据集之间的差异。本文介绍了如何使用哑铃图展示基因的拷贝数变异。 Journal of Translational Medi…

UDP简单聊天室创建

目录 一. 服务端模块实现 二. 处理聊天消息模块实现 三. 调用服务端模块实现 四. 客户端模块实现 五. 效果展示 本文介绍了如何用UDP创建一个简单的聊天室。 一. 服务端模块实现 服务端仍然沿用我们前面的思想&#xff08;高内聚低耦合&#xff09;&#xf…

C语言小tip之函数递归

hello&#xff0c;各位小伙伴们今天我们来学习一下函数递归。 什么是函数递归呢&#xff1f;简单来说就是函数自己来调用自己。函数递归的主要思想是把大事化小&#xff0c;递归包含两层方面&#xff1a;1、递推 2、回归 在使用函数递归的时候要注意包含两个限制条件&#…

Linux 软硬连接

1. 硬链接 实际上并不是通过文件名来找到磁盘上的文件&#xff0c;而是通过inode。在linux中可以让多个文件名对应于同一个 inode&#xff0c;而这种方式就是建立硬链接。硬链接是文件系统中的一种链接类型&#xff0c;它创建了文件的一个额外的目录项&#xff0c;但不占用额外…

全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用

SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型&#xff0c;它综合考虑了土壤-水分-大气以及植被间的相互作用&#xff1b;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程&#xff0c;使其能够精确的模拟土壤中水分的运动&#xff0c;而且耦合了W…

EmguCV学习笔记 C# 7.1 角点检测

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

Ubuntu22.04安装 docker和docker-compose环境

Docker简介 Docker 是一个开源的应用容器引擎&#xff0c;它使开发者能够打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口&#xff08;…

蓝色炫酷碎粒子HTML5导航源码

源码介绍 蓝色炫酷碎粒子HTML5导航源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码获取 蓝色炫酷碎粒…

Halcon提取边缘线段lines_gauss 算子

Halcon提取边缘线段lines_gauss 算子 edges_color_sub_pix和edges_sub_pix两个算子使用边缘滤波器进行边缘检测。还有一个常用的算子lines_gauss算子&#xff0c;也可以用于提取边缘线段&#xff0c;它的鲁棒性非常好&#xff0c;提取出的线段类型是亚像素精度的XLD轮廓。其原…

LLM训练、精调与加速:大型语言模型的高效开发与应用策略

创作不易&#xff0c;您的关注、点赞、收藏和转发是我坚持下去的动力&#xff01; 大家有技术交流指导、论文及技术文档写作指导、项目开发合作的需求可以私信联系我 LLM&#xff08;大型语言模型&#xff09;的训练、精调和加速是当前人工智能研究和应用中的重要话题。下面将…