漫威宇宙入侵免安装绿色中文版
145M · 2025-12-19
某次代码评审,同事吐槽代码里的接口参数校验“又长又乱”:要么到处散落自定义校验器,要么硬塞 if/else 在服务层,跨字段、条件式、还要调用枚举/服务判断的场景尤其难看。
我当场就给大家介绍了一套开源的参数校验组件:SpEL Validator——基于 Spring 表达式,把“何时校验、校验什么”直接写进注解里。既不替代 Jakarta Validation,又能在复杂规则下写得自然、紧贴领域模型,规则和数据终于放到了一起。
@NotNull
private int contentType;
@SpelNotNull(condition = "#this.contentType == 1", message = "语音内容不能为空")
private Object audioContent;
@SpelNotNull(condition = "#this.contentType == 2", message = "视频内容不能为空")
private Object videoContent;
@SpelAssert(assertTrue = "T(cn.sticki.enums.UserStatusEnum).getByCode(#this.userStatus) != null",
message = "用户状态不合法")
private Integer userStatus;
@EnableSpelValidatorBeanRegistrar)@SpelAssert(assertTrue = "@userService.getById(#this.userId) != null", message = "用户不存在")
private Long userId;
一句话概括:在注解里直接写表达式,优雅表达“何时校验、校验什么”,把过去要写成独立校验器/样板代码的逻辑,嵌入在域模型的注解中,让规则与数据天然贴合。
引入依赖(选一):Spring Boot 2.x 用 javax 版;3.x 用 jakarta 版
<dependency>
<groupId>cn.sticki</groupId>
<artifactId>spel-validator-javax</artifactId>
<version>Latest Version</version>
</dependency>
<dependency>
<groupId>cn.sticki</groupId>
<artifactId>spel-validator-jakarta</artifactId>
<version>Latest Version</version>
</dependency>
两步开启
@Valid 或 @Validated@SpelValid示例:
@RestController
@RequestMapping("/example")
public class ExampleController {
@PostMapping("/simple")
public Resp<Void> simple(@RequestBody @Valid SimpleParam p) {
return Resp.ok(null);
}
}
@Data
@SpelValid
public class SimpleParam {
@NotNull
private Boolean switchAudio;
@SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")
private Object audioContent;
}
异常处理仍走 Spring/Jakarta 的主流通道(BindException / MethodArgumentNotValidException),原有代码风格不必改变。
启动注解
@SpelValid:激活 SpEL 约束系统,可放在类、字段、方法参数、构造参数。通用约束注解(均支持 condition、message、group)
@SpelAssert(≈ @AssertTrue)@SpelNotNull / @SpelNull / @SpelNotEmpty / @SpelNotBlank@SpelSize(字符串/集合/Map/数组)@SpelMin / @SpelMax(支持 Number 与 CharSequence;支持 inclusive)@SpelDigits(整数/小数位数控制,支持 Number 与 CharSequence)@SpelPast / @SpelPastOrPresent / @SpelFuture / @SpelFutureOrPresent(覆盖 JDK8 time、Date/Calendar、各 Chrono* 类型)注解均内置国际化消息键,可按需覆盖。
#this.fieldName 引用其他字段。T(全类名).method(...)@beanName.method(...)(需启用 @EnableSpelValidatorBeanRegistrar)这使业务校验与领域知识复用更自然:调用 Enum 工具、Number/BigDecimal 处理、或领域服务,皆可在注解里完成。
condition)condition,决定该注解是否生效。group + spelGroups)@SpelValid(spelGroups = "#this.type") 启用分组语义;group 指定表达式数组。当 spelGroups 与 group 有交集(equals)时生效;group 为空则默认生效。groups 并行不冲突,前者服务于 SpEL 体系,后者仍可用于标准注解。spel-validator-constrain/src/main/resources/cn/sticki/spel/validator 下提供默认翻译(zh、en、ja、ko 等)。ResourceBundleMessageResolver.addBasenames("YourBundle") 将自定义资源包加入优先队列,覆盖默认键。message 中使用 {your.key} 指定键;消息里如含 {} 或 需转义为 \{ \} 与 \。FAQ 的压测数据(基于示例项目、JDK8/SpringBoot 2.7):
工程性细节:
ConcurrentHashMap)减少反射与注解扫描开销。validateObject,便于单元测试或离线任务复用。@NotNull/@Size 等标准注解;当遇到“跨字段/条件式/复杂逻辑/需调用 Bean 或静态方法”时,用对应的 SpEL 注解来补齐空白。@Valid/@Validated + @SpelValid 同时存在,SpEL 校验才会生效。groups 仍可用;SpEL 的 spelGroups/group 更贴近表达式驱动的动态分组需求。message/condition/group 三属性;SpelConstraintValidator<MyAnno>,在 isValid 中取字段值并校验;可覆写 supportType 收窄类型;@SpelConstraint(validatedBy = XxxValidator.class) 关联校验器。SpelValidExecutor 的统一驱动,你的自定义注解天然具备 condition、分组、I18n 能力。Validator 接口也强调线程安全与不可变。@EnableSpelValidatorBeanRegistrar。SpEL Validator 在不破坏现有校验体系的前提下,让“条件式/跨字段/复杂逻辑校验”回归到直观、声明式的写法,规则靠近数据、表达式靠近业务。它与 Jakarta Validation 既兼容又互补:你可以继续使用熟悉的 @NotNull/@Size,同时把“过去很难写顺”的场景交给 @SpelNotNull/@SpelAssert/@SpelSize/... 这套 SpEL 注解去解决。
若你的项目里存在以下任一特征,那么SpEL Validator便值得一试:
项目文档已覆盖入门、注解索引、SpEL 要点、I18n、FAQ 与升级日志,源码结构清晰、测试与报告完善,接入成本低。在线文档:spel-validator.sticki.cn/
现在就把那些分散在各处的“如果…则校验…”逻辑收拢回模型,让校验像业务规则一样清晰可见吧。