同事嫌参数校验太丑?SpEL Validator + IDEA 插件,直接让他闭嘴

大家好,我是阿杆。

参数校验这活儿,写 Java 后端的同学肯定没少干。@NotNull@Size@NotBlank 一把梭,搞定大部分场景没啥问题。

但总有一些场景,这些基本注解是真搞不定的。 比如你想根据某个字段的值来决定另一个字段要不要校验,或者校验一个枚举值是不是合法的,再或者校验的时候需要查一下数据库......

遇到这种情况怎么办?要么在 Service 层写一堆 if/else,要么自定义 ConstraintValidator,代码散落在各处,改起来还容易漏。

所以今天来给大家安利一个我一直在维护的开源参数校验组件------SpEL Validator ,基于 Spring 表达式,把校验规则直接写在注解里,而且最近还推出了配套的 IDEA 插件,开发体验直接拉满。

开源地址:github.com/stick-i/spe...

在线文档:spel-validator.sticki.cn/

SpEL Validator 是什么?

简单来说,SpEL Validator 是一个基于 Spring Expression Language 的参数校验框架 ,它不是要替代你熟悉的那些 @NotNull@NotBlank,而是在 Jakarta Validation 的基础上做增强,把原来搞不定的那些场景给补上。

用过 @NotNull 的同学,上手 SpEL Validator 几乎零成本。直接看几个例子感受下:

条件式校验 :根据 switchAudio 的值决定是否校验 audioContent,只有当 condition 的表达式返回true时才会开启校验

java 复制代码
@NotNull
private Boolean switchAudio;

@SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")
private Object audioContent;

枚举值校验 :调用静态方法判断枚举值是否存在,这里是个断言条件,当 assertTrue 的表达式返回false时校验不通过

java 复制代码
@SpelAssert(assertTrue = " T(cn.sticki.enums.ExampleEnum).getByCode(#this.testEnum) != null ",
    message = "枚举值不合法")
private Integer testEnum;

调用 Spring Bean:直接在表达式中调用已注入的 Bean

java 复制代码
@SpelAssert(assertTrue = "@exampleService.getUser(#this.userId) != null", message = "用户不存在")
private Integer userId;

怎么样,够直观吧?不用再到处翻 Validator 了,打开类就能看到所有的校验规则。

不过,你可能也注意到了------这些 SpEL 表达式是写在字符串里的,这意味着它们没有语法高亮、没有字段补全、字段名拼错了也没人提醒你,得等到运行时才能发现。

但是,注意我要说但是了。

我开发了一个配套的 IDEA 插件:SpEL Validator Support 。装上之后,编写 SpEL 表达式也会有语法高亮、智能补全、Ctrl+Click 跳转、重命名同步、错误实时提醒,全都有。

安装方式:打开 IDEA → Settings → Plugins → 搜索 "SpEL Validator Support" → Install

插件地址:JetBrains Marketplace

下面我用 spel-validator-example 项目来演示框架 + 插件的完整开发体验。

实战演示

准备工作

引入依赖,Spring Boot 3.x 用 jakarta 版:

xml 复制代码
<dependency>
  <groupId>cn.sticki</groupId>
  <artifactId>spel-validator-jakarta</artifactId>
  <version>Latest Version</version>
</dependency>

Spring Boot 2.x 的同学把 jakarta 换成 javax 就行。

然后去 IDEA 的 Plugins 里搜索安装 SpEL Validator Support 插件,重启 IDEA。

案例一:条件式校验 + 智能补全

这是示例项目中最基础的一个场景------SimpleExampleParamVo

java 复制代码
@Data
@SpelValid
public class SimpleExampleParamVo {

    @NotNull
    private Boolean switchAudio;

    @SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")
    private Object audioContent;

    @SpelAssert(assertTrue = " T(cn.sticki.validator.spel.example.enums.ExampleEnum).getByCode(#this.testEnum) != null ",
        message = "枚举值不合法")
    private Integer testEnum;

    @SpelAssert(assertTrue = "@exampleService.getUser(#this.userId) != null", message = "用户不存在")
    private Integer userId;
}

这个类里就包含了三种之前不好处理的校验场景:switchAudio 为 true 时才校验 audioContent;通过静态方法校验枚举值是否合法;调用 Spring Bean 查一下用户是否存在。全都写在注解里,没有一行 if/else。

装了插件之后,你会发现 SpEL 表达式有了语法高亮,字段名、方法调用、类型引用都有颜色区分。输入 #this. 的时候,IDEA 会自动弹出当前类的所有字段。

发起请求看一下校验效果:

json 复制代码
// switchAudio=true 但 audioContent 为空 → 校验不通过
{"switchAudio": true, "audioContent": null}
// 响应:{"code": 400, "message": "audioContent 语音内容不能为空"}

// switchAudio=false → audioContent 不校验,通过
{"switchAudio": false, "audioContent": null}
// 响应:{"code": 200, "message": "成功"}

案例二:分组校验 + 引用导航

再看另一个更有意思的例子,根据 type 分组校验不同的字段。

在某些内容比较多的表单场景下,可能会根据用户选择的不同类型,然后展示不同的字段,这时候就需要根据类型来分组校验不同的字段。

java 复制代码
@Data
@SpelValid(spelGroups = "#this.type")
public class GroupExampleParamVo {

    @NotNull
    @Pattern(regexp = "^text|audio$")
    private String type;

    @SpelNotNull(group = Group.TEXT)
    private Object textContent;

    @SpelNotNull(group = Group.AUDIO)
    private Object audioContent;

    @SpelNotNull // 未指定分组时,默认被校验
    private Integer other;

    static class Group {
        private static final String TEXT = "'text'";
        private static final String AUDIO = "'audio'";
    }
}

type = "text" 时,只有 textContentother 会被校验;当 type = "audio" 时,只有 audioContentother 会被校验。分组逻辑写在注解里就完事了,齐活。

有了插件,你可以 Ctrl+Click 表达式中的字段名,直接跳转到字段定义。也可以在字段上右键 Find Usages,查看它在哪些 SpEL 表达式中被引用了。

案例三:调用 Spring Bean

有时候校验逻辑不是简单的判空或比大小,可能需要查一下数据库才能确定参数合不合法。这时候就需要在表达式里调用 Spring Bean 了。

默认情况下,SpEL Validator 不支持调用 Spring Bean,需要先在启动类上加上 @EnableSpelValidatorBeanRegistrar 注解开启 Bean 支持:

java 复制代码
@EnableSpelValidatorBeanRegistrar
@SpringBootApplication
public class RestApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestApplication.class, args);
    }
}

然后在你的 Service 里写个查询方法(这里简单模拟一下):

java 复制代码
@Service
public class ExampleService {

    public User getUser(int id) {
        User user = new User();
        user.setId(id);
        user.setName("阿杆");
        return user;
    }
}

现在就可以在校验注解中直接引用这个 Bean 了:

java 复制代码
@SpelAssert(assertTrue = "@exampleService.getUser(#this.userId) != null", message = "用户不存在")
private Integer userId;

@exampleService 就是 Spring 容器里那个 Bean 的名字,SpEL 原生语法。

这里只是用简单例子演示一下调用方式。实际开发中你可以调用任何已注入的 Bean 方法来做校验,查缓存查数据库都行。当然了,校验里别搞太重的操作,查个缓存差不多得了,别搞成查了三张表还调了两个远程接口😅。

有了插件的加持,@exampleService 同样能被识别和高亮。如果你不小心写错了 Bean 名或字段名,插件也会给你标红提醒。

更多能力速览

除了上面演示的场景,SpEL Validator 还提供了丰富的约束注解:

注解 说明 对标 Jakarta
@SpelAssert 逻辑断言 ---
@SpelNotNull / @SpelNull 空值校验 @NotNull / @Null
@SpelNotEmpty / @SpelNotBlank 非空校验 @NotEmpty / @NotBlank
@SpelSize 长度校验 @Size
@SpelMin / @SpelMax 数值范围 @Min / @Max
@SpelDigits 数字精度 @Digits
@SpelFuture / @SpelPast 时间校验 @Future / @Past

所有注解都支持 condition(条件开关)和 group(分组),也支持国际化消息message = "{key}" 即可根据 Accept-Language 自动切换语言。

如果有自定义约束注解的需求,只要加上 @SpelConstraint 元注解,IDEA 插件同样能识别并提供智能支持。

最后

OK,差不多就介绍到这里了。总结起来就是:SpEL Validator 负责把校验能力补齐,IDEA 插件负责让你写表达式的时候不再抓瞎,两个搭一起用体验还是很舒服的。

如果你的项目里经常碰到跨字段校验、条件式校验这类场景,不妨试试看,接入成本很低的。

贴一下相关链接:

如果觉得还不错,顺手给项目点个 ⭐️ 吧~ 你的 star 就是我继续肝下去的动力!

相关推荐
用户685453759776925 分钟前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo32 分钟前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
南果梨1 小时前
OpenClaw 完整教程!从安装到使用(官方脚本版)
前端·git·开源
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo1 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
后端AI实验室2 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai