Spring Boot 参数校验

JSR-303

JSR-303 是 Java EE6中的一项子规范,叫做 Bean Validation,为 JavaBean 验证定义了相应的元数据模型和API,通过使用 xml 可以对原有的元数据信息进行覆盖和扩展,在应用程序中,通过使用 BeanValidation 或是自定义的 constraint即可确保数据模型的准确性。constraint 可以附加到字段,getter方法,类或者接口上面。对于一些特定的需求,用户还可以很容易的自定义constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。JSR-303 的依赖为:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

常用注解

注解 释意
@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) 必须符合指定的正则表达式
@Email 必须是邮箱地址
@Length 字符串大小必须在指定范围内
@NotEmpty 字符串必须非空
@Range 必须在合适范围内

JSR-303 使用

校验异常全局处理(封装异常报错信息返回)

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result onException(Exception e) {
        List<ObjectError> allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
        StringBuilder msg = new StringBuilder();
        for (ObjectError allError : allErrors) {
            FieldError fieldError = (FieldError)allError;
            msg.append(fieldError.getDefaultMessage()+";");
        }
        return new Result().setCode(2000).setMsg(msg.toString());
    }
}

简单校验

在参数接收类属性上添加校验注解

@Data
public class ArticleDto {

    @Min(value = 1, message = "id不能小于1")
    @Max(value = 100, message = "id不能大于100")
    private Integer id;

    @NotEmpty(message = "文章名不能为空")
    private String name;

    @NotEmpty(message = "出版社不能为空")
    private String publish;

    @Past
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date publishDate;
}

同一个属性可以添加多个约束注解。如例子中的 @Min 和 @Max
在 controller 请求方法接收参数地方加上 @Validated 注解,表示该参数需要进行参数校验操作。

@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping("testArticle")
    public Result testArticle(@Validated @RequestBody ArticleDto articleDto) {
        System.out.println(articleDto);
        return Result.ok();
    }
}

在参数校验失败时后端会立即对错误信息进行返回并停止后面的处理操作。

{
    "code": 2000,
    "msg": "需要是一个过去的时间;文章名不能为空;",
    "data": null
}

分组校验

新增文章时不需要文章 id,因为文章 id 一般是后端或者数据库自动生成,但是修改文章时需要上传文章 id 来唯一标识某一篇文章内容,并且新增和修改都是使用的同一个 DTO 进行接收参数。

此时需要对这个文章参数校验进行分组,新增文章是一个分组,修改文章又是另一个分组,以此来区分文章 id 是否执行 @Null 和 @NotNull 校验。

所有的校验注解都有一个 groups 属性来指定分组。

dto 类属性添加注解,并指定校验分组

@Data
public class ArticleDto {

    @Min(value = 1, message = "id不能小于1")
    @Max(value = 100, message = "id不能大于100")
    @NotNull(groups = UpdateArticleGroup.class)
    @Null(groups = AddArticleGroup.class)
    private Integer id;

    // 可以指定两个分组都进行校验
    @NotEmpty(message = "文章名不能为空", groups = {UpdateArticleGroup.class, AddArticleGroup.class})
    private String name;

    @NotEmpty(message = "出版社不能为空", groups = {UpdateArticleGroup.class, AddArticleGroup.class})
    private String publish;

    @Past(groups = {UpdateArticleGroup.class, AddArticleGroup.class})
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date publishDate;

    // 添加文章校验分组
    public interface AddArticleGroup{}

    // 修改文章校验分组
    public interface UpdateArticleGroup{}
}

controller 指定使用哪个校验分组进行校验

@RestController
@RequestMapping("test")
public class TestController {

    @PostMapping("addArticle")
    public Result testArticle(@Validated(value = ArticleDto.AddArticleGroup.class) @RequestBody ArticleDto articleDto) {
        System.out.println(articleDto);
        return Result.ok();
    }

    @PostMapping("updateArticle")
    Result updateArticle(@Validated(value = ArticleDto.UpdateArticleGroup.class) @RequestBody ArticleDto articleDto) {
        System.out.println(articleDto);
        return Result.ok();
    }
}

嵌套校验

嵌套校验:一个实体中包含另外一个实体,并且这些实体都需要进行参数校验。
如文章的作者,一般也是一个单独的实体。

package com.example.demo.jsr.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.Date;

/**
 * @version 1.0
 * @date 2023/2/13 14:53
 * Description:
 */
@Data
public class ArticleDto {

	// ...文章其它属性...
	
	// 作者信息	
    @Valid
    @NotNull(message = "作者信息不能为空", groups = {UpdateArticleGroup.class, AddArticleGroup.class})
    private AuthorDto author;
}

只需要在嵌套的实体属性标注 @Valid 注解,则其中的属性也将会得到校验,否则不会校验

作者 dto 校验

@Data
public class AuthorDto {

    @NotNull(groups = ArticleDto.UpdateArticleGroup.class)
    @Null(groups = ArticleDto.AddArticleGroup.class)
    private Integer id;

    @NotEmpty(groups = {ArticleDto.AddArticleGroup.class, ArticleDto.UpdateArticleGroup.class})
    private String name;

    public interface AddAuthorGroup{}

    public interface UpdateAuthorGroup{}
}

嵌套校验针对分组校验仍然生效,如果嵌套的实体类中指定的分组与 @Validated 注解指定的分组不同,则不会进行校验。

自定义校验注解

例子:传入的数字要在列举的范围中,否则校验失败

首先,自定义一个校验注解

@Documented
@Constraint(validatedBy = {EnumValuesConstraintValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@NotNull(message = "不能为空")
public @interface EnumValues {

	String message() default "传入的值不在范围内";

    // 分组
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    // 可传入的值
    int[] values() default {};
}

根据 Bean Validation API 规范,message,groups 和 payload 属性是必须的,自定义添加了一个 values 属性来接收限制的范围。

自定义校验注解可以复用内嵌的注解,比如 @EnumValues 上边标注了 @NotNull 注解,这样 @EnumValues 就兼具了 @NotNull 的功能。

然后创建 @Constraint 注解指定的校验器

public class EnumValuesConstraintValidator implements ConstraintValidator<EnumValues, Integer> {

    // 存储枚举的值
    private Set<Integer> ints = new HashSet<>();

    // 初始化方法
    // EnumValues 校验的注解
    @Override
    public void initialize(EnumValues values) {
        for (int item : values.values()) {
            ints.add(item);
        }
    }

    /**
     * @param value 入参传的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return ints.contains(value);
    }
}

在 dto 中使用自定义注解校验

@Data
public class AuthorDto {

    @EnumValues(values = {1,2}, message = "性别只能传1或2")
    private Integer gender;
}
posted @ 2023-02-13 16:59  wangms821  阅读(82)  评论(0编辑  收藏  举报