我写了一套几乎无敌的参数校验组件!基于 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会有一定的性能损耗。
开启约束校验
需要满足以下两个条件,才会对带注解的元素进行校验:
- 在接口参数上使用
@Valid或@Validated注解 - 在实体类上使用
@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);}}
自定义约束注解
- 在注解上使用
@SpelConstraint,并指定验证器。 - 然后给注解添加上三个固定的字段 message 、group、condition。
- 实现验证器


具体实现方式可以参考 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」
前言 大家好,我是阿杆,不是阿轩。 参数校验这个东西,很多情况下都是比较简单的,用 NotNull、Size 等注解就可以解决绝大多数场景,但也有一些场景是这些基本注解解决不了的,只能用一些其他的方式处理&…...
输入序列太长 gan CGAN
transformer序列长度大导致计算复杂度高 GAN 2. 训练过程 第一阶段:固定「判别器D」,训练「生成器G」。使用一个性能不错的判别器,G不断生成“假数据”,然后给这个D去判断。开始时候,G还很弱,所以很容易被…...
uni-app scroll-view隐藏滚动条的小细节 兼容主流浏览器
开端 想写个横向滚动的列表适配浏览器,主要就是隐藏一下滚动条在手机上美观一点。 但是使用uni-app官方文档建议的::-webkit-scrollbar在目标标签时发现没生效。 .scroll-view_H::-webkit-scrollbar{display: none; }解决 F12看了一下,原来编译到浏览…...
Java常用API之LinkedList类解读
写在开头:本文用于作者学习我将官方文档中LinkedList 1.6版本中类中绝大部分API全测了一遍并打印了结果,日拱一卒,常看常新。 自己补充了一些对该数据结构的理解,如有不对的地方,请各位指正,谢谢。 首先&…...
移动端自适应
基本实现核心思想 基本原则上是,布局更多地使用flex,然后尺寸使用rem,vw,vh为单位如果是根据不同的屏幕需要有不同的布局了,一般通过检测屏幕尺寸换不同的站点或者媒体查询使用css rem 以html字体太小为1rem的大小&…...
自动化运维工具-Ansible
一、Ansible概述 Ansible是一种基于python开发的自动化运维工具,它只需要在服务端安装ansible,无需在每个客户端安装客户端程序,通过ssh的方式来进行客户端服务器的管理,基于模块来实现批量数据配置、批量设备部署以及批量命令执…...
力扣:62. 不同路径
62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径&…...
store内路由跳转router.push
选择action还是mutation 选择action mutation 是用来改变state的,不应该包含路由相关操作mutation是同步执行的,不应该包含异步操作,而路由是异步操作 action中进行路由跳转 因为vuex中没有this,所以不能用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项目时,遇到一个报错: 最关键的代码如下: Error: error:0308010C:digital envelope routines::unsupportedat new Hash (node:internal/crypto/hash:80:19)百度后发现是node版本的问题。 在昨天我确实操作了一下node&…...
golang系统内置函数整理
go语言中有很多系统内置的函数, 为了方便学习,对系统内置函数的函数定义 入参和返回值做如下整理,以方便学习和记忆。 Go语言系统级别的内置函数不多,但是包含的知识点可不少,是学习go语言说必须要搞明白的基础知识 …...
武汉星起航:五对一服务体系,助力创业者成功进军跨境电商市场
随着全球化的深入发展和互联网的普及,跨境电商已成为越来越多国内创业者的首选。然而,跨境电商市场的复杂性和多变性使得许多新手创业者望而却步。在这样的背景下,武汉星起航电子商务有限公司以其独特的五对一服务体系,为创业者提…...
C++常用库函数——strcmp、strchr
1、strcmp:比较两个字符串的值是否相等 例如 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,s2指向a2,strcmp表示比较s1和s…...
vue3怎么使用vant的IndexBar 索引栏
Vant 是一个基于 Vue 的移动端 UI 组件库,它提供了许多常见的移动端组件,包括 IndexBar 索引栏。以下是如何在 Vue 3 中使用 Vant 的 IndexBar 索引栏的步骤: 安装 Vant 如果你还没有安装 Vant,你可以使用 npm 或 yarn 来安装它…...
VMware常见问题(技巧)总结
目录 问题虚拟机中windows11如何开启vt 虚拟化?虚拟机Windows 11 中的相机使用失败问题? 待续、更新中 问题 虚拟机中windows11如何开启vt 虚拟化? 编辑设置—打对钩 选对正确镜像( 可翻看以往文章,有提到) 虚拟机Windows 11 中的相机使用失败问题? 1 . 没安装合适的驱动 …...
VS Code 保存+格式化代码
在 VSCode 中,使用 Ctrl S 快捷键直接保存并格式化代码: 打开 VSCode 的设置界面:File -> Preferences -> Settings在设置界面搜索框中输入“format on save”,勾选“Editor: Format On Save”选项,表示在保存…...
word启动缓慢之Baidu Netdisk Word Addin
word启动足足花了7秒钟,你知道我这7秒是怎么过来的吗? 原因就是我们可爱的百度网盘等APP,在我们安装客户端时,默认安装了Office加载项,不仅在菜单栏上加上了一个丑陋的字眼,也拖慢了word启动速度........ 解…...
获取波形极值与间距并显示
获取并显示波形的极值与极值间距 1、流程 1、通过signal.find_peaks获取极大值 2、获取极大值下标 3、获取极大值对应的值 4、获取极大值的下标间距(就是隔多远有一个极大值) 5、获取极大值间距的标准差、方差、均值、最大值 6、图形展示波形图并标记极大值2、效果图 3、示…...
视频素材哪个app好?8个视频素材库免费使用
视频内容已成为现代传播中不可或缺的一部分,具备卓越的视频素材对于提升任何媒体作品的质量和吸引力尤为关键。这里列举的一系列精挑细选的全球视频素材网站,旨在为您的商业广告、社交媒体更新或任何其他类型的视觉项目提供最佳支持。 1. 蛙学府&#x…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
