参数校验这个东西,很多情况下都是比较简单的,用 @NotNull
、@Size
等注解就可以解决绝大多数场景,但也有一些场景是这些基本注解解决不了的,只能用一些其他的方式处理,这样就导致参数校验变成了多层,其实是不利于代码维护的。
于是乎,我写了一套几乎可以满足任何场景的参数校验组件,非常好用,安利给大家。
枚举值字段校验:
@SpelAssert(assertTrue = " T(cn.sticki.enums.UserStatusEnum).getByCode(#this.userStatus) != null ", message = "用户状态不合法")
private Integer userStatus;
多字段联合校验:
@NotNull
private Integer contentType;
@SpelNotNull(condition = "#this.contentType == 1", message = "语音内容不能为空")
private Object audioContent;
@SpelNotNull(condition = "#this.contentType == 2", message = "视频内容不能为空")
private Object videoContent;
复杂逻辑校验,调用静态方法:
// 中文算两个字符,英文算一个字符,要求总长度不超过 10
// 调用外部静态方法进行校验
@SpelAssert(assertTrue = "T(cn.sticki.util.StringUtil).getLength(#this.userName) <= 10", message = "用户名长度不能超过10")
private String userName;
调用 Spring Bean(需要使用 @EnableSpelValidatorBeanRegistrar 开启Spring Bean支持):
// 这里只是简单举例,实际开发中不建议这样判断用户是否存在
@SpelAssert(assertTrue = "@userService.getById(#this.userId) != null", message = "用户不存在")
private Long userId;
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
- 强大的参数校验功能,几乎支持所有场景下的参数校验。
- 扩展自 javax.validation 包,只新增不修改,无缝集成到项目中。
- 基于 SpEL(Spring Expression Language) 表达式,支持复杂的校验逻辑。
- 支持调用 Spring Bean,可在表达式中使用注入过的 Spring Bean。
- 支持自定义校验注解,可根据业务需求自定义校验逻辑。
- 无需额外的异常处理,校验失败时会上报到 javax.validation 的异常体系中。
- 简单易用,使用方式几乎与 javax.validation 一致,学习成本低,上手快。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/yudao-cloud
- 视频教程:https://doc.iocoder.cn/video/
目前仅测试了 JDK8 环境,理论上来说 JDK8+ 应该都是支持的。
添加依赖
Latest Version: 0.0.2-beta
<dependency>
<groupId>cn.stickigroupId>
<artifactId>spel-validatorartifactId>
<version>Latest Versionversion>
dependency>
<dependency>
<groupId>org.hibernate.validatorgroupId>
<artifactId>hibernate-validatorartifactId>
<version>${hibernate-validator.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${spring-boot-starter-web.version}version>
dependency>
在接口参数上使用 @Valid
或 @Validated
注解
@RestController
@RequestMapping("/example")
public class ExampleController {
/**
* 简单校验示例
*/
@PostMapping("/simple")
public Resp simple(@RequestBody @Valid SimpleExampleParamVo simpleExampleParamVo) {
return Resp.ok(null);
}
}
在实体类上使用 @SpelValid
注解,同时在需要校验的字段上使用 @SpelNotNull
等约束注解
@Data
@SpelValid
public class SimpleExampleParamVo {
@NotNull
private Boolean switchAudio;
/**
* 当 switchAudio 为 true 时,校验 audioContent,audioContent 不能为null
*/
@SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")
private Object audioContent;
}
添加全局异常处理器,处理校验异常
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
public Resp<Void> handleBindException(BindException ex) {
String msg = ex.getFieldErrors().stream()
.map(error -> error.getField() + " " + error.getDefaultMessage())
.reduce((s1, s2) -> s1 + "," + s2)
.orElse("");
return new Resp<>(400, msg);
}
}
示例一:@SpelNotNull 校验不通过
请求体:
{
"switchAudio": true,
"audioContent": null
}
响应体
{
"code": 400,
"message": "audioContent 语音内容不能为空",
"data": null
}
示例二:校验通过
请求体
{
"switchAudio": false,
"audioContent": null
}
响应体
{
"code": 200,
"message": "成功",
"data": null
}
示例三:@NotNull 校验不通过
请求体
{
"switchAudio": null,
"audioContent": null
}
响应体
{
"code": 400,
"message": "switchAudio 不能为null",
"data": null
}
注意:本组件的目的不是代替 javax.validation
的校验注解,而是作为一个扩展,方便某些场景下的参数校验。能够使用 javax.validation
的场景就不要使用 spel-validator
,因为 spel-validator
会有一定的性能损耗。
需要满足以下两个条件,才会对带注解的元素进行校验:
- 在接口参数上使用
@Valid
或 @Validated
注解
如果只满足第一个条件,那么只会对带 @NotNull
、@NotEmpty
、@NotBlank
等注解的元素进行校验。
如果只满足第二个条件,那么不会对任何元素进行校验。
这是因为 @SpelValid
注解是基于 javax.validation.Constraint
实现的,只有在 @Valid
或 @Validated
注解的支持下才会生效。而 spel-validator
提供的约束注解是基于 @SpelValid
进行扫描校验的,只有在 @SpelValid
注解生效的情况下才会执行约束校验。
目前支持的约束注解有:
注解 | 说明 | 对标 javax.validation |
---|
@SpelAssert | 逻辑断言校验 | 无 |
@SpelNotNull | 非 null 校验 | @NotNull |
@SpelNotEmpty | 集合、字符串、数组大小非空校验 | @NotEmpty |
@SpelNotBlank | 字符串非空串校验 | @NotBlank |
@SpelNull | 必须为 null 校验 | @Null |
@SpelSize | 集合、字符串、数组长度校验 | @Size |
每个约束注解都包含三个默认的属性:
group
:分组条件,支持 SpEL 表达式,当分组条件满足时,才会对带注解的元素进行校验。condition
:约束开启条件,支持 SpEL 表达式,当 表达式为空 或 计算结果为true 时,才会对带注解的元素进行校验。
默认情况下,解析器无法识别 SpEL 表达式中的 Spring Bean。
如果需要在 SpEL 表达式中调用 Spring Bean,需要在启动类上添加 @EnableSpelValidatorBeanRegistrar
注解, 开启 Spring Bean 支持。
@EnableSpelValidatorBeanRegistrar
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 在注解上使用
@SpelConstraint
,并指定验证器。 - 然后给注解添加上三个固定的字段 message 、group、condition。
具体实现方式可以参考 cn.sticki.validator.spel.SpelConstraint
类。
如果你使用过 javax.validation
的自定义约束注解,那么你会发现 SpEL Validator
的自定义约束注解几乎与 javax.validation
一致。
我也写了一个简单的示例项目,但目前还不太完善,只有基本的使用方法:
- https://github.com/stick-i/spel-validator-example
性能上我目前还没有进行测试,但代码里使用了很多的反射,会有一定的损耗,后面我准备多加一些缓存,尽量降低性能上的影响。
在项目中使用SpEL的时候,有一个很怪异的现象:
我给这两个字段都标记了 @Language("SpEL")
,但是只有 condition
可以识别,我不知道这算不算idea的bug,我目前使用的idea版本是 IntelliJ IDEA 2024.1 (Ultimate Edition),有懂的朋友请帮忙解答一下。
GitHub地址:https://github.com/stick-i/spel-validator