首页 Spring Boot 注解方式进行表单验证
文章
取消

Spring Boot 注解方式进行表单验证

在实际业务开发中肯定会遇到参数校验的场景,参数少还好说,在遇到大量参数校验的场景时,单写 if-else 就很痛苦了,因此 Spring 为我们提供了通过注解方式进行参数校验的功能。

1. 引入依赖

如果是 Spring Boot 2.3 以后版本,需要引入 spring-boot-starter-validation2.3之前已经集成在 spring-boot-starter-web 中。

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. 使用方式

基础使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 使用 @Validated 注解标识需要对参数 knowledgeVO 进行校验
*/
@PostMapping("/add")
public Result<Void> addKnowledge(@RequestBody @Validated KnowledgeVO knowledgeVO) {
    return Result.ok();
}

@PostMapping("/update")
@Operation(summary = "修改")
public Result<Void> updateKnowledge(@Parameter(description = "知识参数", required = true) @RequestBody @Validated(
    {OnUpdate.class}) KnowledgeVO knowledgeVO) {
    knowledgeService.save(knowledgeVO);
    return Result.ok();
}

/**
* 在属性上添加注解标识需要对该字段进行校验
*/
@Data
@Accessors(chain = true)
public class KnowledgeVO {

    @NotBlank(message = "名称不能为空")
    @Size(min = 1, max = 100)
    private String name;

    @NotEmpty(message = "属性列表不能为空")
    @Valid
    private List<KnowledgeAttributeVO> attributes;

}

@Data
@Accessors(chain = true)
public class KnowledgeAttributeVO {

    @NotNull(message = "id 不能为空", groups = OnUpdate.class)
    private Long id;

    @NotBlank(message = "属性名不能为空")
    private String name;

    @NotBlank(message = "属性值不能为空")
    private String value;

}

常用注解

注解说明
@Null限制只能为null
@NotNull限制必须不为null,一般用来校验Integer类型不能为空
@AssertFalse限制必须为false
@AssertTrue限制必须为true
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,
小数部分的位数不能超过fraction
@Future限制必须是一个将来的日期
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Past限制必须是一个过去的日期
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在min到max之间
@Past验证注解的元素值(日期类型)比当前时间早
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0),一般用
来校验List类型不能为空
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),一般用
来校验String类型不能为空,不同于@NotEmpty,@NotBlank只应用于
字符串且在比较时会去除字符串的空格
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
@Valid添加到属性上时表示需要对属性进行循环校验

进阶使用(校验分组)

当出现这种场景,当新增时不需要校验 id,修改时需要校验 id 不为空,这怎么搞呢?通过分组的方式,代码见下图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 新建类作为分组标识
*/
public interface OnUpdate extends Default {

}

@Data
@Accessors(chain = true)
@ApiModel("知识属性")
public class KnowledgeAttributeVO {

    /**
    * 通过 groups 声明 id 处于 OnUpdate 分组,同时 OnUpdate 集成自 Default 接口
    * (所有没有声明 groups 的注解均属 Default 分组),因此使用 OnUpdate 分组的注解进
    * 行校验时也会对 Default 分组的字段进行校验
    */
    @NotNull(message = "id 不能为空", groups = OnUpdate.class)
    private Long id;

    @NotBlank(message = "属性名不能为空")
    private String name;

    @NotBlank(message = "属性值不能为空")
    private String value;

}

/**
* @Validated({OnUpdate.class}) 表示使用 OnUpdate 分组的注解进行表单校验
*/
@PostMapping("/update")
public Result<Void> updateKnowledge(@RequestBody @Validated({OnUpdate.class}) KnowledgeVO knowledgeVO) {
    knowledgeService.save(knowledgeVO);
    return Result.ok();
}

整合代码后运行代码执行结果如下: /spring-boot-vlidation-run-result.png

再看后台运行日志

2022-11-17 20:48:45.828  WARN 93016 --- [nio-8613-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.mgdaas.lidihuo.bean.response.Result<java.lang.Void> com.mgdaas.lidihuo.controller.KnowledgeController.updateKnowledge(com.mgdaas.lidihuo.bean.request.KnowledgeVO) with 3 errors: [Field error in object 'knowledgeVO' on field 'attributes': rejected value [null]; codes [NotEmpty.knowledgeVO.attributes,NotEmpty.attributes,NotEmpty.java.util.List,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [knowledgeVO.attributes,attributes]; arguments []; default message [attributes]]; default message [属性列表不能为空]] [Field error in object 'knowledgeVO' on field 'id': rejected value [null]; codes [NotNull.knowledgeVO.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [knowledgeVO.id,id]; arguments []; default message [id]]; default message [id 不能为空]] [Field error in object 'knowledgeVO' on field 'name': rejected value [null]; codes [NotBlank.knowledgeVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [knowledgeVO.name,name]; arguments []; default message [name]]; default message [名称不能为空]] ]

在实际开发中参数异常信息需要返回出去,因此我们可以结合 ControllerAdivce 来进行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandlerController {

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result<Void> handleMethodArgumentNotValidException(HttpServletRequest request,
        MethodArgumentNotValidException ex) {
        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
        String message = fieldErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.joining(","));
        log.debug("调用接口:{} 发生异常,错误信息:{}", request.getRequestURI(), Throwables.getStackTraceAsString(ex));
        return Result.fail(message);
    }

}

如此我们再看返回结果:

1
2
3
4
5
{
	"code": 400,
	"message": "id 不能为空,名称不能为空,属性列表不能为空",
	"data": null
}

自定义校验

实际业务场景中 Spring 为我提供的注解肯定还是不能完全满足校验需求的,比如说校验某个字段数据是否存在,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 自定义校验注解
*
*/
@Documented
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {CategoryIdExistsValidator.class})
public @interface CategoryIdExists {

    /**
    * 默认错误信息
    */
    String message() default "类别 id 不存在";

    Class[] groups() default {};

    Class[] payload() default {};

}

/**
* 校验逻辑实现类
* 
*/
public class CategoryIdExistsValidator implements ConstraintValidator<CategoryIdExists, Long> {

    @Autowired
    private CategoryRepository categoryRepository;

    @Override
    public boolean isValid(Long value, ConstraintValidatorContext context) {
        // 为空时不校验
        if (value == null) {
            return true;
        }
        // 禁用默认消息
        context.disableDefaultConstraintViolation();

        // 查询数据库
        Optional<Category> optional = categoryRepository.findById(value);
        if (optional.isPresent()) {
            return true;
        }

        // 校验不通过设置错误信息
        context.buildConstraintViolationWithTemplate("类别 id:" + value + "  不存在").addConstraintViolation();
        return false;
    }
}

@Data
@Accessors(chain = true)
@ApiModel("知识类别")
public class CategoryVO {

    @ApiModelProperty("类别 id")
    @NotNull(message = "类别 id 不能为空", groups = {OnUpdate.class})
    @CategoryIdExists(groups = {OnUpdate.class})
    private Long id;

    @NotBlank(message = "类别名称不能为空")
    private String name;

    @CategoryIdExists
    private Long parentId;

}
本文由作者按照 CC BY 4.0 进行授权

Spring Boot 整合 Swagger3

2022-11-18 内炼总结