当前位置: 首页 > news >正文

我写了一套几乎无敌的参数校验组件!基于 SpEL 的参数校验组件「SpEL Validator」

前言

大家好,我是阿杆,不是阿轩。

参数校验这个东西,很多情况下都是比较简单的,用 @NotNull@Size 等注解就可以解决绝大多数场景,但也有一些场景是这些基本注解解决不了的,只能用一些其他的方式处理,这样就导致参数校验变成了多层,其实是不利于代码维护的。

于是乎,我写了一套几乎可以满足任何场景的参数校验组件,非常好用,安利给大家。

GitHub:stick-i/spel-validator: 一个强大的 Java 参数校验包,基于 SpEL 实现,扩展自 javax.validation 包,几乎支持所有场景下的参数校验。 (github.com)

💡 它解决了什么问题?

  • 枚举值字段校验:

    @SpelAssert(assertTrue = " T(cn.sticki.enums.UserStatusEnum).getByCode(#this.userStatus) != null ", message = "用户状态不合法")
    private Integer userStatus;
    
  • 多字段联合校验:

    @NotNull
    private Integer contentType;@SpelNotNull(condition = "#this.contentType == 1", message = "语音内容不能为空")
    private Object audioContent;@SpelNotNull(condition = "#this.contentType == 2", message = "视频内容不能为空")
    private Object videoContent;
    
  • 复杂逻辑校验,调用静态方法:

    // 中文算两个字符,英文算一个字符,要求总长度不超过 10
    // 调用外部静态方法进行校验
    @SpelAssert(assertTrue = "T(cn.sticki.util.StringUtil).getLength(#this.userName) <= 10", message = "用户名长度不能超过10")
    private String userName;
    
  • 调用 Spring Bean(需要使用 @EnableSpelValidatorBeanRegistrar 开启Spring Bean支持):

    // 这里只是简单举例,实际开发中不建议这样判断用户是否存在
    @SpelAssert(assertTrue = "@userService.getById(#this.userId) != null", message = "用户不存在")
    private Long userId;
    
  • 更多使用场景,欢迎探索和补充!

📝 特点

  • 强大的参数校验功能,几乎支持所有场景下的参数校验。
  • 扩展自 javax.validation 包,只新增不修改,无缝集成到项目中。
  • 基于 SpEL(Spring Expression Language) 表达式,支持复杂的校验逻辑。
  • 支持调用 Spring Bean,可在表达式中使用注入过的 Spring Bean。
  • 校验时基于整个对象,支持对象内字段间的校验逻辑。
  • 支持自定义校验注解,可根据业务需求自定义校验逻辑。
  • 无需额外的异常处理,校验失败时会上报到 javax.validation 的异常体系中。
  • 简单易用,使用方式几乎与 javax.validation 一致,学习成本低,上手快。

🎈 环境

目前仅测试了 JDK8 环境,理论上来说 JDK8+ 应该都是支持的。

📦 快速开始

  • 添加依赖

    Latest Version: 0.0.2-beta

    <dependency><groupId>cn.sticki</groupId><artifactId>spel-validator</artifactId><version>Latest Version</version>
    </dependency><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>${hibernate-validator.version}</version>
    </dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring-boot-starter-web.version}</version>
    </dependency>
    
  • 在接口参数上使用 @Valid@Validated 注解

    @RestController
    @RequestMapping("/example")
    public class ExampleController {/*** 简单校验示例*/@PostMapping("/simple")public Resp<Void> simple(@RequestBody @Valid SimpleExampleParamVo simpleExampleParamVo) {return Resp.ok(null);}}
    
  • 在实体类上使用 @SpelValid 注解,同时在需要校验的字段上使用 @SpelNotNull 等约束注解

    @Data
    @SpelValid
    public class SimpleExampleParamVo {@NotNullprivate Boolean switchAudio;/*** 当 switchAudio 为 true 时,校验 audioContent,audioContent 不能为null*/@SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")private Object audioContent;}
    
  • 添加全局异常处理器,处理校验异常

    @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);}}
    
  • 发起请求,即可看到校验结果

    • 示例一:@SpelNotNull 校验不通过

      请求体

      {"switchAudio": true,"audioContent": null
      }
      

      响应体

      {"code": 400,"message": "audioContent 语音内容不能为空","data": null
      }
      
    • 示例二:校验通过

      请求体

      {"switchAudio": false,"audioContent": null
      }
      

      响应体

      {"code": 200,"message": "成功","data": null
      }
      
    • 示例三:@NotNull 校验不通过

      请求体

      {"switchAudio": null,"audioContent": null
      }
      

      响应体

      {"code": 400,"message": "switchAudio 不能为null","data": null
      }
      

📖 使用指南

注意:本组件的目的不是代替 javax.validation 的校验注解,而是作为一个扩展,方便某些场景下的参数校验。能够使用 javax.validation 的场景就不要使用 spel-validator ,因为 spel-validator 会有一定的性能损耗。

开启约束校验

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

  1. 在接口参数上使用 @Valid@Validated 注解
  2. 在实体类上使用 @SpelValid 注解

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

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

这是因为 @SpelValid 注解是基于 javax.validation.Constraint 实现的,只有在 @Valid@Validated 注解的支持下才会生效。
spel-validator 提供的约束注解是基于 @SpelValid 进行扫描校验的,只有在 @SpelValid 注解生效的情况下才会执行约束校验。

使用约束注解

目前支持的约束注解有:

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

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

  • message:校验失败时的提示信息。
  • group:分组条件,支持 SpEL 表达式,当分组条件满足时,才会对带注解的元素进行校验。
  • condition:约束开启条件,支持 SpEL 表达式,当 表达式为空 或 计算结果为true 时,才会对带注解的元素进行校验。

调用 Spring Bean

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

如果需要在 SpEL 表达式中调用 Spring Bean,需要在启动类上添加 @EnableSpelValidatorBeanRegistrar 注解,
开启 Spring Bean 支持。


@EnableSpelValidatorBeanRegistrar
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

自定义约束注解

  1. 在注解上使用 @SpelConstraint ,并指定验证器。
  2. 然后给注解添加上三个固定的字段 message 、group、condition。
  3. 实现验证器

自定义示例约束示例-定义约束注解

自定义约束示例-实现验证器

具体实现方式可以参考 cn.sticki.validator.spel.SpelConstraint 类。

如果你使用过 javax.validation 的自定义约束注解,那么你会发现 SpEL Validator 的自定义约束注解几乎与 javax.validation
一致。

📦 示例项目

我也写了一个简单的示例项目,但目前还不太完善,只有基本的使用方法:

  • https://github.com/stick-i/spel-validator-example

最后

关于性能

性能上我目前还没有进行测试,但代码里使用了很多的反射,会有一定的损耗,后面我准备多加一些缓存,尽量降低性能上的影响。

一个奇怪的现象

在项目中使用SpEL的时候,有一个很怪异的现象:

我给这两个字段都标记了 @Language("SpEL"),但是只有 condition 可以识别,我不知道这算不算idea的bug,我目前使用的idea版本是 IntelliJ IDEA 2024.1 (Ultimate Edition),有懂的朋友请帮忙解答一下。

最后的最后

贴一下GitHub地址:https://github.com/stick-i/spel-validator (顺手点个star呀~)

欢迎大家进行体验,有任何疑问欢迎一起讨论。

相关文章:

我写了一套几乎无敌的参数校验组件!基于 SpEL 的参数校验组件「SpEL Validator」

前言 大家好&#xff0c;我是阿杆&#xff0c;不是阿轩。 参数校验这个东西&#xff0c;很多情况下都是比较简单的&#xff0c;用 NotNull、Size 等注解就可以解决绝大多数场景&#xff0c;但也有一些场景是这些基本注解解决不了的&#xff0c;只能用一些其他的方式处理&…...

输入序列太长 gan CGAN

transformer序列长度大导致计算复杂度高 GAN 2. 训练过程 第一阶段&#xff1a;固定「判别器D」&#xff0c;训练「生成器G」。使用一个性能不错的判别器&#xff0c;G不断生成“假数据”&#xff0c;然后给这个D去判断。开始时候&#xff0c;G还很弱&#xff0c;所以很容易被…...

uni-app scroll-view隐藏滚动条的小细节 兼容主流浏览器

开端 想写个横向滚动的列表适配浏览器&#xff0c;主要就是隐藏一下滚动条在手机上美观一点。 但是使用uni-app官方文档建议的::-webkit-scrollbar在目标标签时发现没生效。 .scroll-view_H::-webkit-scrollbar{display: none; }解决 F12看了一下&#xff0c;原来编译到浏览…...

Java常用API之LinkedList类解读

写在开头&#xff1a;本文用于作者学习我将官方文档中LinkedList 1.6版本中类中绝大部分API全测了一遍并打印了结果&#xff0c;日拱一卒&#xff0c;常看常新。 自己补充了一些对该数据结构的理解&#xff0c;如有不对的地方&#xff0c;请各位指正&#xff0c;谢谢。 首先&…...

移动端自适应

基本实现核心思想 基本原则上是&#xff0c;布局更多地使用flex&#xff0c;然后尺寸使用rem&#xff0c;vw&#xff0c;vh为单位如果是根据不同的屏幕需要有不同的布局了&#xff0c;一般通过检测屏幕尺寸换不同的站点或者媒体查询使用css rem 以html字体太小为1rem的大小&…...

自动化运维工具-Ansible

一、Ansible概述 Ansible是一种基于python开发的自动化运维工具&#xff0c;它只需要在服务端安装ansible&#xff0c;无需在每个客户端安装客户端程序&#xff0c;通过ssh的方式来进行客户端服务器的管理&#xff0c;基于模块来实现批量数据配置、批量设备部署以及批量命令执…...

力扣:62. 不同路径

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…...

store内路由跳转router.push

选择action还是mutation 选择action mutation 是用来改变state的&#xff0c;不应该包含路由相关操作mutation是同步执行的&#xff0c;不应该包含异步操作&#xff0c;而路由是异步操作 action中进行路由跳转 因为vuex中没有this&#xff0c;所以不能用this.$router&#…...

ChatGPT Web Midjourney一键集成最新版

准备工具 服务器一台 推荐使用浪浪云服务器 稳定 安全 有保障 chatgpt api 推荐好用白嫖的api 项目演示 项目部署 浏览器访问casaos 添加软件原添加 https://gitee.com/langlangy_1/CasaOS-AppStore-LangLangy/raw/master/chatmjd.zip 安装此软件 等待安装 安装后再桌面设置…...

springboot mongodb分片集群事务

前置 mongodb分片集群想要使用事务,需要对应分片没有仲裁节点 代码 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId><version>2.1.0.RELEASE</version></d…...

node报错——解决Error: error:0308010C:digital envelope routines::unsupported——亲测可用

今天在打包vue2项目时&#xff0c;遇到一个报错&#xff1a; 最关键的代码如下&#xff1a; Error: error:0308010C:digital envelope routines::unsupportedat new Hash (node:internal/crypto/hash:80:19)百度后发现是node版本的问题。 在昨天我确实操作了一下node&…...

golang系统内置函数整理

go语言中有很多系统内置的函数&#xff0c; 为了方便学习&#xff0c;对系统内置函数的函数定义 入参和返回值做如下整理&#xff0c;以方便学习和记忆。 Go语言系统级别的内置函数不多&#xff0c;但是包含的知识点可不少&#xff0c;是学习go语言说必须要搞明白的基础知识 …...

武汉星起航:五对一服务体系,助力创业者成功进军跨境电商市场

随着全球化的深入发展和互联网的普及&#xff0c;跨境电商已成为越来越多国内创业者的首选。然而&#xff0c;跨境电商市场的复杂性和多变性使得许多新手创业者望而却步。在这样的背景下&#xff0c;武汉星起航电子商务有限公司以其独特的五对一服务体系&#xff0c;为创业者提…...

C++常用库函数——strcmp、strchr

1、strcmp&#xff1a;比较两个字符串的值是否相等 例如 char a1[6] "AbDeG",*s1 a1;char a2[6] "AbdEg",* s2 a2;s1 2;s2 2;printf("%d \n", strcmp(s1, s2));return(0); s1指向a1&#xff0c;s2指向a2&#xff0c;strcmp表示比较s1和s…...

vue3怎么使用vant的IndexBar 索引栏

Vant 是一个基于 Vue 的移动端 UI 组件库&#xff0c;它提供了许多常见的移动端组件&#xff0c;包括 IndexBar 索引栏。以下是如何在 Vue 3 中使用 Vant 的 IndexBar 索引栏的步骤&#xff1a; 安装 Vant 如果你还没有安装 Vant&#xff0c;你可以使用 npm 或 yarn 来安装它…...

VMware常见问题(技巧)总结

目录 问题虚拟机中windows11如何开启vt 虚拟化?虚拟机Windows 11 中的相机使用失败问题? 待续、更新中 问题 虚拟机中windows11如何开启vt 虚拟化? 编辑设置—打对钩 选对正确镜像( 可翻看以往文章,有提到) 虚拟机Windows 11 中的相机使用失败问题? 1 . 没安装合适的驱动 …...

VS Code 保存+格式化代码

在 VSCode 中&#xff0c;使用 Ctrl S 快捷键直接保存并格式化代码&#xff1a; 打开 VSCode 的设置界面&#xff1a;File -> Preferences -> Settings在设置界面搜索框中输入“format on save”&#xff0c;勾选“Editor: Format On Save”选项&#xff0c;表示在保存…...

word启动缓慢之Baidu Netdisk Word Addin

word启动足足花了7秒钟&#xff0c;你知道我这7秒是怎么过来的吗&#xff1f; 原因就是我们可爱的百度网盘等APP&#xff0c;在我们安装客户端时&#xff0c;默认安装了Office加载项&#xff0c;不仅在菜单栏上加上了一个丑陋的字眼&#xff0c;也拖慢了word启动速度........ 解…...

获取波形极值与间距并显示

获取并显示波形的极值与极值间距 1、流程 1、通过signal.find_peaks获取极大值 2、获取极大值下标 3、获取极大值对应的值 4、获取极大值的下标间距(就是隔多远有一个极大值) 5、获取极大值间距的标准差、方差、均值、最大值 6、图形展示波形图并标记极大值2、效果图 3、示…...

视频素材哪个app好?8个视频素材库免费使用

视频内容已成为现代传播中不可或缺的一部分&#xff0c;具备卓越的视频素材对于提升任何媒体作品的质量和吸引力尤为关键。这里列举的一系列精挑细选的全球视频素材网站&#xff0c;旨在为您的商业广告、社交媒体更新或任何其他类型的视觉项目提供最佳支持。 1. 蛙学府&#x…...

【图灵完备(Turing Complete)】五、从逻辑门到LEG:指令集与条件跳转的构建

1. 从逻辑门到处理器&#xff1a;LEG架构的诞生之路 记得我第一次用面包板搭建简单逻辑电路时&#xff0c;连个LED灯闪烁都要折腾半天。而现在我们要做的&#xff0c;是把这些基础逻辑门像乐高积木一样拼接成真正的处理器核心。LEG架构的设计初衷就是要解决原始图灵机指令宽度受…...

LiuJuan Z-Image Generator真实案例:为独立音乐人生成专辑封面人像全流程

LiuJuan Z-Image Generator真实案例&#xff1a;为独立音乐人生成专辑封面人像全流程 最近&#xff0c;一位独立音乐人朋友找到我&#xff0c;说他想为自己的新专辑设计一个封面。预算有限&#xff0c;请不起专业画师&#xff0c;但又不想要那些千篇一律的模板。他想要一张能体…...

Nunchaku-flux-1-dev技术解析:深入理解其背后的深度学习网络架构

Nunchaku-flux-1-dev技术解析&#xff1a;深入理解其背后的深度学习网络架构 最近在AI编程和图像生成圈子里&#xff0c;FLUX.1 [dev]这个名字被讨论得越来越多。作为其社区衍生版本&#xff0c;Nunchaku-flux-1-dev自然也吸引了大量技术爱好者的目光。大家可能已经体验过它生…...

League-Toolkit:基于LCU API的英雄联盟本地化效率工具集

League-Toolkit&#xff1a;基于LCU API的英雄联盟本地化效率工具集 【免费下载链接】League-Toolkit 兴趣使然的、简单易用的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 在英雄联盟的…...

毕业季论文救星:深度解析百考通AI如何智能攻克文献综述与开题报告

又到一年毕业季&#xff0c;无数莘莘学子在为自己学术生涯的“终极答卷”——毕业论文而挑灯夜战。其中&#xff0c;文献综述的浩如烟海与开题报告的千头万绪&#xff0c;无疑是横亘在大多数同学面前的两座大山。你是否也曾面对海量文献不知如何筛选梳理&#xff1f;是否为构建…...

【C++ 面试突击 · 07】大厂高频面试题:从菱形继承到const与constexpr的博弈深度解析

目录 1. 什么是菱形继承&#xff1f;怎么解决菱形继承&#xff1f; 2. 如何定义一个只能在堆上&#xff08;栈上&#xff09;生成对象的类&#xff1f; 3. C 强制类型转换运算符有哪些&#xff1f; 4. C 中的类型推导&#xff08;auto&#xff09;是如何工作的&#xff1f;…...

Eigen矩阵打印踩坑记:从乱码到优雅输出的3个关键技巧与一个隐藏Bug

Eigen矩阵打印踩坑记&#xff1a;从乱码到优雅输出的3个关键技巧与一个隐藏Bug 第一次在ROS项目里调试Eigen矩阵时&#xff0c;我盯着终端里歪歪扭扭的数字对齐和突然冒出的科学计数法&#xff0c;花了整整两小时才意识到这不是算法问题&#xff0c;而是输出格式在作祟。Eigen作…...

从“偏科生”GPT-3到“全能选手”:聊聊MMLU基准如何推动大模型进化

从“偏科生”到“全能选手”&#xff1a;MMLU基准如何重塑大模型进化路径 当GPT-3在2020年以1750亿参数震惊世界时&#xff0c;人们很快发现这个"天才"存在明显的知识盲区——它在某些专业领域的表现堪比专家&#xff0c;却在另一些基础学科上失误频频。这种"偏…...

从零到一:在Simulink中构建SVPWM仿真模型的实践指南

1. 为什么选择Simulink搭建SVPWM模型&#xff1f; 第一次接触电机控制时&#xff0c;我被各种专业术语搞得晕头转向。直到发现Simulink这个可视化工具&#xff0c;才真正理解了SVPWM&#xff08;空间矢量脉宽调制&#xff09;的精髓。就像用乐高积木搭建城堡&#xff0c;Simuli…...

淘宝母婴购物数据可视化分析:从数据清洗到商业洞察

1. 淘宝母婴数据清洗实战&#xff1a;从原始数据到分析就绪 做数据分析最头疼的就是拿到一堆乱七八糟的原始数据&#xff0c;淘宝母婴数据也不例外。我最近处理过一批天池比赛的脱敏数据&#xff0c;光是清洗环节就踩了不少坑。先说说最基础的CSV导入&#xff0c;用pandas的rea…...