大家好,我是阿杆。

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

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

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

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

SpEL Validator 是什么?

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

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

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

@NotNull
private Boolean switchAudio;

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

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

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

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

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

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

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

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

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

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

实战演示

准备工作

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

<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

@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 会自动弹出当前类的所有字段。

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

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

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

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

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

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

@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 支持:

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

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

@Service
public class ExampleService {

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

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

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

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

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

有了插件的加持,@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 插件负责让你写表达式的时候不再抓瞎,两个搭一起用体验还是很舒服的。

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

贴一下相关链接:

  • GitHub:github.com/stick-i/spe…
  • 在线文档:spel-validator.sticki.cn/
  • IDEA 插件:JetBrains Marketplace
  • 示例项目:github.com/stick-i/spe…

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

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com