参数校验是Java后端开发的日常。@NotNull、@Size、@NotBlank 这些标准注解足以应付大多数简单场景。
但当需求变得复杂时,它们就力不从心了。比如,需要根据字段A的值来决定是否校验字段B;或者校验一个枚举编码是否合法;甚至需要查询数据库才能完成验证。这时你会怎么做?
常见做法是在Service层写一堆if/else逻辑,或者为每个复杂场景自定义ConstraintValidator。这不仅让代码变得臃肿,校验逻辑散落各处,维护和修改时也容易遗漏。
今天介绍一个能优雅解决上述问题的开源方案:SpEL Validator。它是一个基于Spring Expression Language (SpEL) 的参数校验增强框架,并且配备了专属的 IDEA插件,让开发和调试体验大幅提升。
开源地址:https://github.com/stick-i/spel-validator
在线文档:https://spel-validator.sticki.cn/
SpEL Validator 是什么?
简单来说,SpEL Validator 是一个基于 Spring Expression Language 的参数校验框架。它并非要取代标准的 Jakarta Bean Validation 注解,而是在其基础上进行功能增强,专门处理那些标准注解搞不定的复杂校验场景。
对于熟悉 @NotNull 的开发者而言,SpEL Validator 的上手成本极低。它的核心思想是:将校验规则以 SpEL 表达式的形式直接写在注解属性里。先看几个例子感受一下:
条件式校验:根据 switchAudio 字段的值决定是否校验 audioContent 字段。
@NotNull
private Boolean switchAudio;
@SpelNotNull(condition = “#this.switchAudio == true”, message = “语音内容不能为空”)
private Object audioContent;
枚举值校验:通过调用静态方法,验证传入的编码是否对应一个有效的枚举项。
@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;
这样一来,所有校验规则都清晰地定义在实体类上,一目了然,无需在业务代码中混杂校验逻辑。
不过,细心的你可能发现了问题:这些SpEL表达式是写在字符串里的。没有语法高亮、没有代码补全、拼写错误也无法在编码期发现,只能等到运行时才会报错。
这正是配套 IDEA插件——SpEL Validator Support 的价值所在。安装后,编写SpEL表达式将获得完整的IDE支持:语法高亮、智能补全、Ctrl+Click跳转、字段重命名同步、实时错误提示等。
安装方式:打开 IDEA → Settings → Plugins → 搜索 “SpEL Validator Support” → Install
插件地址:JetBrains Marketplace
下面,我们通过一个示例项目来完整演示框架与插件结合带来的开发体验。
实战演示
准备工作
首先,在项目中引入依赖。对于 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中安装并重启启用 SpEL Validator Support 插件。
案例一:条件式校验与智能补全
我们来看一个基础示例 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 非空。
- 枚举校验:通过静态方法验证
testEnum 是否为合法枚举值。
- 服务调用:调用Spring Bean
exampleService 来检查用户是否存在。
安装了插件后,编写SpEL表达式体验完全不同:表达式有了语法高亮,输入 #this. 时会自动弹出当前类的字段列表供你选择。

发起请求,校验逻辑按预期工作:
// switchAudio=true 但 audioContent 为空 → 校验不通过
{“switchAudio”: true, “audioContent”: null}
// 响应:{“code”: 400, “message”: “audioContent 语音内容不能为空”}
// switchAudio=false → audioContent 不校验,通过
{“switchAudio”: false, “audioContent”: null}
// 响应:{“code”: 200, “message”: “成功”}
案例二:分组校验与引用导航
在某些动态表单场景中,需要根据用户选择的类型来校验不同的字段集。SpEL Validator 的分组功能可以优雅地实现这一点。
@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” 时,校验 textContent 和 other。
- 当
type = “audio” 时,校验 audioContent 和 other。
插件的另一个实用功能是导航。你可以 Ctrl+Click 表达式中的 #this.type 或字段名,直接跳转到该字段的定义。同样,在字段上使用 Find Usages 可以快速找到所有引用该字段的SpEL表达式。
案例三:调用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的方法,执行如检查用户状态、验证权限等操作。不过需注意,校验逻辑应保持轻量,避免在验证环节进行复杂的联表查询或远程调用。
插件的支持同样覆盖了Bean调用。@exampleService 会被高亮识别,如果Bean名或方法名拼写错误,IDE会立即给出红色波浪线提示。

更多能力速览
除了上述核心场景,SpEL Validator 提供了一系列丰富的约束注解,几乎对标了Jakarta Validation的标准注解集:
| 注解 |
说明 |
对标 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 = “{your.message.key}”,即可根据请求的 Accept-Language 头部自动切换提示语言。
对于有自定义注解需求的场景,你可以在自定义注解上添加 @SpelConstraint 元注解,IDEA插件同样能够识别并提供智能支持。
总结
SpEL Validator 框架与 IDEA 插件的组合,为解决Java后端复杂的参数校验问题提供了一个优雅且高效的方案。框架弥补了标准校验在逻辑表达能力上的不足,而插件则彻底改善了开发者在编写和调试SpEL表达式时的体验。
如果你的项目正受困于繁琐的条件校验、跨字段验证或需要集成外部服务的校验逻辑,不妨尝试接入SpEL Validator。它的设计力求简洁,与现有Jakarta Validation标准兼容,接入成本很低。
相关资源链接:
希望这个工具能帮助你写出更清晰、更易维护的校验代码。欢迎在云栈社区与其他开发者交流更多关于提升开发效率的实践与工具。