TRON:催化剂免安装绿色中文版
5.61G · 2025-10-17
某次代码评审,同事吐槽代码里的接口参数校验“又长又乱”:要么到处散落自定义校验器,要么硬塞 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/
现在就把那些分散在各处的“如果…则校验…”逻辑收拢回模型,让校验像业务规则一样清晰可见吧。