在日常的开发中,我们总是需要去校验一些请求参数,为了提高我们的开发速度且避免在业务逻辑中参杂太多的参数校验逻辑,我们就会使用到spring-validated
这个第三方参数校验库。本文是对常用的校验方式进行的一个总结!
本文主要有以下内容:
- 单个参数校验
- 嵌套对象的校验
- 自定义分组校验
本文适合和我一样的Java
初级开发人员和刚了解到Spring-validated
的Javaer
前置条件:
Spring boot
创建工程环节省略Spring Boot 2.3
开始需要自己手动引入spring-boot-starter-validation
,以前是自动集成的。Spring Boot 2.x
版本 在引入验证时,导入的注解包是javax
开头的 [1]。Spring Boot 3.x
版本 在引入验证时,导入的注解包是jakarta
开头的。
在pom.xml
中添加依赖:自己选择合适的版本号!
org.springframework.boot
spring-boot-starter-validation
单个参数的校验
- 在进行单个参数的校验时,我们需要在类上添加
@Validated
注解,否则将没有验证作用。
ID或String类型的单参数校验
通常在进行单个参数校验时,使用@RequestParam
注解时,就有一定的校验作用。
@GetMapping("single_param.do")
public void singleParameter(@RequestParam("username") String username, @RequestParam("userId") Integer userId) {
log.info("singleParameter username = {},userId = {}", username, userId);
}
这种方式是URL传参,而@RequestParam
的require
属性默认为true
表示必须传递!如果不传递将会出现如下错误提示:Required request parameter 'username' for method parameter type String is not present
。这种错误一般不会犯!
使用Spring-validated
进行数据校验,代码如下,
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
@GetMapping("single_param2.do")
public void singleParameter2(@NotBlank String username, @NotNull Integer userId) {
log.info("singleParameter2 username = {},userId = {}", username, userId);
}
@NotBlank
:表示字符串不能为null
且.trim()
的长度不能为0.@NotNull
:表示不能为null
注意:要在类名上添加@Validated
,否则校验将不会起作用!
此时在进行参数传递时,如果不传递参数将会出现nested exception is javax.validation.ConstraintViolationException: singleParameter2.username: 不能为空]
异常。因此我们需要以localhost:8080/validate/single_param2.do?username=username&userId=10
这种方式进行请求才可以得到我们想要的结果。
注:如果是在Postman
中,填写参数是这样填写的
是能通过上述验证的,这里是这个字符串的值是""
,所以是没有问题的!
集合校验
首先看接口代码:
@GetMapping("list.do")
public void simpleList(List list) {
log.info("simpleList list = {}", list);
}
如果Java
接口是这种方式写的,这样在传递参数不管是直接传xxx?1,2,3
还是xxx?list=1,2,3
是都会出错的。错误信息如下:No primary or single unique constructor found for interface java.util.List
。我们需要在参数前使用@RequestParam
注解才可以正常接收!,这种情况下,不传递参数也是没有问题的。即URL?list=
是可以正常访问的,但是不能没有list
这个参数!此时在接口上添加@NotEmpty
注解。代码如下:
@GetMapping("list.do")
public void simpleList(@RequestParam @NotEmpty List list) {
log.info("simpleList list = {}", list);}
@NotEmpty
:源码[2]解释如下:The annotated element must not be null nor empty.不能为空 Supported types are:
- CharSequence (length of character sequence is evaluated):字符串长度大于0
- Collection (collection size is evaluated):集合:size > 0
- Map (map size is evaluated):Map size>0
- Array (array length is evaluated):数组长度 > 0
因此在使用@NotBlank
后,如果请求方式还是这种url?list=
将还会出错,错误信息如下:javax.validation.ConstraintViolationException: simpleList.list: 不能为空
。
单个参数以及集合的情况就讨论完毕,接下来就是嵌套对象的验证。
嵌套对象的校验
首先定义一个参数请求对象:
@Data
public class RequestArgs {
private Integer userId;
@NotBlank(message = "名称为必填项")
private String nickName;
@Email(message = "请填写正确的邮箱地址")
private String email;
@Length(message = "密码长度应在6~12位之间", min = 6, max = 12)
@NotBlank
private String password;
}
表单提交
多个参数的传递一般有两种方式:表单或者JSON
,首先看表单的传递
@PostMapping("nest.do")
public void nestObj(RequestArgs args) {
log.info("nestObj: {}", args);
}
这就是一个常见的Post
请求接口,此时我们不在参数前添加@Validated
时,这里的验证将不会起作用,因此需要添加@Validate
注解,模拟表单提交,如果此时请求参数不符合我们填写的验证规则将会出现错误:Postman
测试如下
错误信息:org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestArgs.password,password]; arguments []; default message [password]]; default message [不能为空]]
。这是表单提交的情况!
JSON提交
// RequestBody 校验
@PostMapping("nest2.do")
public void nestObj2(@Validated @RequestBody RequestArgs args) {
log.info("nestObj2: {}", args);
}
JSON
方式的校验和上面大抵是相同的,而之所以变成了Json
方式提交只不过是用了@RequestBody
注解罢了。缺少nickName
必填项
错误信息:arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestArgs.nickName,nickName]; arguments []; default message [nickName]]; default message [名称为必填项]] ]
自定义分组校验
该使用场景在于在日常的开发中,比如新增和修改时,对同一个实体类具有不同的验证规则,如新增一条记录时,我们通常在后端服务自己生成一个主键ID
,而在修改已有记录时,需要前端传递一个主键ID
让我们进行数据库检索并修改!这里的实现方式方式有两种:
- 分别为这两种情况定义两个实体类,分别定义自己的校验规则
- 在同一个类上通过定义不同的校验规则实现。
接下来就通过方式二来实现这个需求:
首先自定义一个接口并继承Default
接口:代码如下
public interface CostumerValidGroup extends Default {
interface Curd extends CostumerValidGroup {
interface Insert extends Curd {
}
interface Update extends Curd {
}
interface Query extends Curd {
}
interface Delete extends Curd {
}
}
}
此时修改我们的RequestArgs.java
:指定userId
在新增时不需要填写,在修改时需要填写。
@Data
public class RequestArgs {
@NotNull(groups = CostumerValidGroup.Curd.Update.class, message = "userId不能为null")
@Null(groups = CostumerValidGroup.Curd.Insert.class)
private Integer userId;
@NotBlank(message = "名称为必填项")
private String nickName;
@Email(message = "请填写正确的邮箱地址")
private String email;
@Length(message = "密码长度应在6~12位之间", min = 6, max = 12)
@NotBlank
private String password;
}
这样我们就可以进行参数校验,在使用时也比较简单,代码如下:
// 自定义校验规则:新增验证规则
@PostMapping("insert.do")
public void insert(@RequestBody @Validated(value = CostumerValidGroup.Curd.Insert.class) RequestArgs args){
log.info("insert: {}", args);
}
// 自定义校验规则:验证更新
@PostMapping("update.do")
public void update(@RequestBody @Validated(value = CostumerValidGroup.Curd.Update.class) RequestArgs args){
log.info("update: {}", args);
}
我们只需要在@Validated
注解上指定校验规则即可!
总结:到这里常见的几种校验场景一级介绍完毕,下面介绍Validated
常用注解:
@Null
被注释的元素必须为null@NotNull
被注释的元素不能为null,可以为空字符串@AssertTrue
被注释的元素必须为true@AssertFalse
被注释的元素必须为false@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max,min)
被注释的元素的大小必须在指定的范围内。@Digits(integer,fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内@Past
被注释的元素必须是一个过去的日期@Future
被注释的元素必须是一个将来的日期@Pattern(value)
被注释的元素必须符合指定的正则表达式。@Length
被注释的字符串的大小必须在指定的范围内@Range
被注释的元素必须在合适的范围内@NotEmpty
用在集合类上,不能为null,并且长度必须大于0@NotBlank
只能作用在String上,不能为null,而且调用trim()后,长度必须大于0
参考资料:
[1].SpringBoot with Jakarta Validation Api not validating with @Valid Annotation
[2].@NotEmpty
源码